4 minutes
Pwntool Tips 2
ELF and ROP Modules
Pwntools gives us the ability to interact with ELFs and shared libraries in a programmatic way.
ELF
fit
One of the Class-level members I want to talk about is fit
. I’ve had to create lines in my
exploit code that look like this:
buf_len = 128
pad_len = buf_len - (len(gadgets) + len(mprotect))
payload = gadgets
payload += mprotect
payload += "A" * pad_len
payload += canary
payload += "A" * 16 # junk
payload += jmprsp
fit
allows you to be declarative about where each of your exploit components should be in your
payload.
The same payload, using fit
:
payload = fit({
0: gadgets,
8: mprotect,
128: canary,
144: jmprsp
})
All padding between the declared sections is inserted for you. Bonus, it uses the same pattern
that cyclic
uses, so if your binary crashes during the exploit, the resulting address in the
crash screen of GDB can be inserted into cyclic_find
and it will tell you where the cause of the
crash is in your payload. Much like using pattern_create
to find an initial buffer length. I’ll
go into cyclic
in another blog post.
In [1]: from pwn import *
In [2]: exp = fit({
...: 4: "ZZZZ",
...: 12: "XXXX"
...: })
In [3]: exp
Out[3]: 'aaaaZZZZcaaaXXXX'
symbols
symbols
will return a dotdict
of symbol-to-address mappings. sym
is a convenience
alias to symbols
. A dotdict
is a class within pwntools
that allows dotted access to the
underlying python dict
.
In [1]: from pwn import *
In [2]: e = ELF("pwnable")
In [3]: e.symbols
Out[3]:
{u'__gmon_start__': 4207552,
u'__libc_start_main': 4207544,
...snip...
u'read': 4198480,
u'setvbuf': 4198512,
u'stdout': 4207640,
u'strcmp': 4198496,
u'strlen': 4198444}
In [4]: e.sym.read
Out[4]: 4198480
search
search
takes a sequence of bytes and returns an iterator of possible matches. Handy if you want
to get the location of say “/bin/sh” inside of libc
, or even just to find specific instructions
you might want to use in your payload.
In [1]: from pwn import *
In [2]: e = ELF('/lib/libc.so.6')
In [3]: next(e.search("/bin/sh"))
Out[3]: 1618340
Finding a JMP RSP (ff e4
) instruction:
In [1]: from pwn import *
In [2]: e = ELF('pwnable')
In [3]: next(e.search("\xff\xe4"))
Out[3]: 159281
ROP
The ROP module facilitates creating ROP chains by creating a python-style call API of sorts for
calling symbols located in the binary. We’ll use mprotect
for our example.
From man 3 mprotect
:
NAME
mprotect — set protection of memory mapping
SYNOPSIS
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
call
Call will allow you to call the symbol you designate as the first param, and take subsequent
arguments to the callee as a list. The function doesn’t return anything, but modifies the
instance of the ROP class. This allows for you to continue to chain more calls together. Once
you’re ready, you can bytes(rop)
or rop.chain()
to get the resulting payload. As a convenience,
symbols are also directly callable from the ROP
instance.
In [1]: from pwn import *
In [2]: context.arch = "amd64"
In [3]: rop = ROP('./haxme')
In [4]: rop.dump()
Out[4]: ''
In [5]: rop.call('mprotect', [0x12345678, 0x1000, 0x7])
In [6]: print(rop.dump())
0x0000: 0x43e369 pop rdx; pop rsi; ret
0x0008: 0x7 [arg2] rdx = 7
0x0010: 0x1000 [arg1] rsi = 4096
0x0018: 0x401d93 pop rdi; ret
0x0020: 0x12345678 [arg0] rdi = 305419896
0x0030: 0x43e369 pop rdx; pop rsi; ret
In [7]: rop.read(0, 0x1234, 0x100)
In [8]: print(rop.dump())
0x0000: 0x43e369 pop rdx; pop rsi; ret
0x0008: 0x7 [arg2] rdx = 7
0x0010: 0x1000 [arg1] rsi = 4096
0x0018: 0x401d93 pop rdi; ret
0x0020: 0x12345678 [arg0] rdi = 305419896
0x0028: 0x43bf50 mprotect
####
0x0030: 0x43e369 pop rdx; pop rsi; ret
0x0038: 0x100 [arg2] rdx = 256
0x0040: 0x1234 [arg1] rsi = 4660
0x0048: 0x401d93 pop rdi; ret
0x0050: 0x0 [arg0] rdi = 0
0x0058: 0x43b3e0 read
gadgets
and find_gadget
Upon instantiation of a ROP object, you may see log output mention something like “Loading
Gadgets”. It’s self explanatory. I do want to mention that pwntools
gadget finder isn’t as
robust as something like
ropper.
That said, it’s still a very helpful and useful function. gadgets
contains your dict of gadget
objects and find_gadget
is simply a convenience methods for searching the gadgets
dict.
In [1]: from pwn import *
In [2]: rop = ROP('./haxme')
In [3]: len(rop.gadgets)
Out[3]: 109
In [4]: gdt = rop.find_gadget(["pop rsi"])
In [5]: gdt
Out[5]: Gadget(0x4006ab, [u'pop rsi', u'pop r15', u'pop rbp', u'ret'], [u'rsi', u'r15', u'rbp'], 0x10)
In [6]: gdt.address
Out[6]: 4196011L
SROP
When attempting to call something like mprotect
in a binary that doesn’t explicitly have a symbol
for it, pwntools
will attempt a syscall instead, using SIGRET/SROP. I’ve written about this
technique in the past.
Happy refactoring, and see you next time!