217 lines
5.7 KiB
Markdown
217 lines
5.7 KiB
Markdown
Table of Contents
|
|
=================
|
|
|
|
* [ELFs](#elfs)
|
|
* [Loading ELF files](#loading-elf-files)
|
|
* [Using Symbols](#using-symbols)
|
|
* [Changing the Base Address](#changing-the-base-address)
|
|
* [Reading ELF Files](#reading-elf-files)
|
|
* [Patching ELF Files](#patching-elf-files)
|
|
* [Searching ELF Files](#searching-elf-files)
|
|
* [Building ELF Files](#building-elf-files)
|
|
* [Running and Debugging ELF Files](#running-and-debugging-elf-files)
|
|
|
|
# ELFs
|
|
|
|
Pwntools makes interacting with ELF files relatively straightforward, via the `ELF` class. You can find the full documentation on [RTD](https://pwntools.readthedocs.org/en/latest/elf.html).
|
|
|
|
## Loading ELF files
|
|
|
|
ELF files are loaded by path. Upon being loaded, some security-relevant attributes about the file are printed.
|
|
|
|
```py
|
|
from pwn import *
|
|
|
|
e = ELF('/bin/bash')
|
|
# [*] '/bin/bash'
|
|
# Arch: amd64-64-little
|
|
# RELRO: Partial RELRO
|
|
# Stack: Canary found
|
|
# NX: NX enabled
|
|
# PIE: No PIE
|
|
# FORTIFY: Enabled
|
|
```
|
|
|
|
## Using Symbols
|
|
|
|
ELF files have a few different sets of symbols available, each contained in a dictionary of `{name: data}`.
|
|
|
|
- `ELF.symbols` lists all known symbols, including those below. Preference is given the PLT entries over GOT entries.
|
|
- `ELF.got` only contains GOT entries
|
|
- `ELF.plt` only contains PLT entries
|
|
- `ELF.functions` only contains functions (requires DWARF symbols)
|
|
|
|
This is very useful in keeping exploits robust, by removing the need to hard-code addresses.
|
|
|
|
```py
|
|
from pwn import *
|
|
|
|
e = ELF('/bin/bash')
|
|
|
|
print "%#x -> license" % e.symbols['bash_license']
|
|
print "%#x -> execve" % e.symbols['execve']
|
|
print "%#x -> got.execve" % e.got['execve']
|
|
print "%#x -> plt.execve" % e.plt['execve']
|
|
print "%#x -> list_all_jobs" % e.functions['list_all_jobs'].address
|
|
```
|
|
|
|
This would print something like the following:
|
|
|
|
```
|
|
0x4ba738 -> license
|
|
0x41db60 -> execve
|
|
0x6f0318 -> got.execve
|
|
0x41db60 -> plt.execve
|
|
0x446420 -> list_all_jobs
|
|
```
|
|
|
|
## Changing the Base Address
|
|
|
|
Changing the base address of the ELF file (e.g. to adjust for ASLR) is very straightforward. Let's change the base address of `bash`, and see all of the symbols change.
|
|
|
|
```py
|
|
from pwn import *
|
|
|
|
e = ELF('/bin/bash')
|
|
|
|
print "%#x -> base address" % e.address
|
|
print "%#x -> entry point" % e.entry
|
|
print "%#x -> execve" % e.symbols['execve']
|
|
|
|
print "---"
|
|
e.address = 0x12340000
|
|
|
|
print "%#x -> base address" % e.address
|
|
print "%#x -> entry point" % e.entry
|
|
print "%#x -> execve" % e.symbols['execve']
|
|
```
|
|
|
|
This should print something like:
|
|
|
|
```
|
|
0x400000 -> base address
|
|
0x42020b -> entry point
|
|
0x41db60 -> execve
|
|
---
|
|
0x12340000 -> base address
|
|
0x1236020b -> entry point
|
|
0x1235db60 -> execve
|
|
```
|
|
|
|
## Reading ELF Files
|
|
|
|
We can directly interact with the ELF as if it were loaded into memory, using `read`, `write`, and functions named identically to that in the `packing` module. Additionally, you can see the disassembly via the `disasm` method.
|
|
|
|
```py
|
|
from pwn import *
|
|
|
|
e = ELF('/bin/bash')
|
|
|
|
print repr(e.read(e.address, 4))
|
|
|
|
p_license = e.symbols['bash_license']
|
|
license = e.unpack(p_license)
|
|
print "%#x -> %#x" % (p_license, license)
|
|
|
|
print e.read(license, 14)
|
|
print e.disasm(e.symbols['main'], 12)
|
|
```
|
|
|
|
This prints something like:
|
|
|
|
```
|
|
'\x7fELF'
|
|
0x4ba738 -> 0x4ba640
|
|
License GPLv3+
|
|
41eab0: 41 57 push r15
|
|
41eab2: 41 56 push r14
|
|
41eab4: 41 55 push r13
|
|
```
|
|
|
|
## Patching ELF Files
|
|
|
|
Patching ELF files is just as simple.
|
|
|
|
```py
|
|
from pwn import *
|
|
|
|
e = ELF('/bin/bash')
|
|
|
|
# Cause a debug break on the 'exit' command
|
|
e.asm(e.symbols['exit_builtin'], 'int3')
|
|
|
|
# Disable chdir and just print it out instead
|
|
e.pack(e.got['chdir'], e.plt['puts'])
|
|
|
|
# Change the license
|
|
p_license = e.symbols['bash_license']
|
|
license = e.unpack(p_license)
|
|
e.write(license, 'Hello, world!\n\x00')
|
|
|
|
e.save('./bash-modified')
|
|
```
|
|
|
|
We can then run our modified version of bash.
|
|
|
|
```
|
|
$ chmod +x ./bash-modified
|
|
$ ./bash-modified -c 'exit'
|
|
Trace/breakpoint trap (core dumped)
|
|
$ ./bash-modified --version | grep "Hello"
|
|
Hello, world!
|
|
$ ./bash-modified -c 'cd "No chdir for you!"'
|
|
/home/user/No chdir for you!
|
|
No chdir for you!
|
|
./bash-modified: line 0: cd: No chdir for you!: No such file or directory
|
|
```
|
|
|
|
## Searching ELF Files
|
|
|
|
Every once in a while, you just need to find some byte sequence. The most common example is searching for e.g. `"/bin/sh\x00"` for an `execve` call.
|
|
The `search` method returns an iterator, allowing you to either take the first result, or keep searching if you need something special (e.g. no bad characters in the address). You can optionally pass a `writable` argument to `search`, indicating it should only return addresses in writable segments.
|
|
|
|
```py
|
|
from pwn import *
|
|
|
|
e = ELF('/bin/bash')
|
|
|
|
for address in e.search('/bin/sh\x00'):
|
|
print hex(address)
|
|
```
|
|
|
|
The above example prints something like:
|
|
|
|
```
|
|
0x420b82
|
|
0x420c5e
|
|
```
|
|
|
|
## Building ELF Files
|
|
|
|
ELF files can be created from scratch relatively easy. All of these functions are context-aware. The relevant functions are `from_bytes` and `from_assembly`. Each returns an `ELF` object, which can easily be saved to file.
|
|
|
|
```
|
|
from pwn import *
|
|
|
|
ELF.from_bytes('\xcc').save('int3-1')
|
|
ELF.from_assembly('int3').save('int3-2')
|
|
ELF.from_assembly('nop', arch='powerpc').save('powerpc-nop')
|
|
```
|
|
|
|
## Running and Debugging ELF Files
|
|
|
|
If you have an `ELF` object, you can run or debug it directly. The following are equivalent:
|
|
|
|
```py
|
|
>>> io = elf.process()
|
|
# vs
|
|
>>> io = process(elf.path)
|
|
```
|
|
|
|
Similarly, you can launch a debugger trivially attached to your ELF. This is super useful when testing shellcode, without the need for a C wrapper to load and debug it.
|
|
|
|
```py
|
|
>>> io = elf.debug()
|
|
# vs
|
|
>>> io = gdb.debug(elf.path)
|
|
``` |