CTF-All-In-One/doc/2.4.1_pwntools.md
2018-08-05 17:43:10 +08:00

571 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 2.4.1 Pwntools
- [安装](#安装)
- [模块简介](#模块简介)
- [使用 Pwntools](#使用-pwntools)
- [Pwntools 在 CTF 中的运用](#pwntools-在-ctf-中的运用)
- [参考资料](#参考资料)
Pwntools 是一个 CTF 框架和漏洞利用开发库,用 Python 开发,由 rapid 设计,旨在让使用者简单快速的编写 exp 脚本。包含了本地执行、远程连接读写、shellcode 生成、ROP 链的构建、ELF 解析、符号泄露众多强大功能。
## 安装
1. 安装binutils
```shell
git clone https://github.com/Gallopsled/pwntools-binutils
sudo apt-get install software-properties-common
sudo apt-add-repository ppa:pwntools/binutils
sudo apt-get update
sudo apt-get install binutils-arm-linux-gnu
```
2. 安装capstone
```shell
git clone https://github.com/aquynh/capstone
cd capstone
make
sudo make install
```
3. 安装pwntools:
```shell
sudo apt-get install libssl-dev
sudo pip install pwntools
```
如果你在使用 Arch Linux则可以通过 AUR 直接安装,这个包目前是由我维护的,如果有什么问题,欢迎与我交流:
```text
$ yaourt -S python2-pwntools
或者
$ yaourt -S python2-pwntools-git
```
但是由于 Arch 没有 PPA 源,如果想要支持更多的体系结构(如 arm, aarch64 等),只能手动编译安装相应的 binutils使用下面的脚本注意将变量 `V``ARCH` 换成你需要的。[binutils](https://ftp.gnu.org/gnu/binutils/)[源码](../src/others/2.4.1_pwntools/binutils.sh)
```bash
#!/usr/bin/env bash
V = 2.29 # binutils version
ARCH = arm # target architecture
cd /tmp
wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.xz
wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.xz.sig
# gpg --keyserver keys.gnupg.net --recv-keys C3126D3B4AE55E93
# gpg --verify binutils-$V.tar.xz.sig
tar xf binutils-$V.tar.xz
mkdir binutils-build
cd binutils-build
export AR=ar
export AS=as
../binutils-$V/configure \
--prefix=/usr/local \
--target=$ARCH-unknown-linux-gnu \
--disable-static \
--disable-multilib \
--disable-werror \
--disable-nls
make
sudo make install
```
测试安装是否成功:
```python
>>> from pwn import *
>>> asm('nop')
'\x90'
>>> asm('nop', arch='arm')
'\x00\xf0 \xe3'
```
## 模块简介
Pwntools 分为两个模块,一个是 `pwn`,简单地使用 `from pwn import *` 即可将所有子模块和一些常用的系统库导入到当前命名空间中,是专门针对 CTF 比赛的;而另一个模块是 `pwnlib`,它更推荐你仅仅导入需要的子模块,常用于基于 pwntools 的开发。
下面是 pwnlib 的一些子模块(常用模块和函数加粗显示):
- `adb`:安卓调试桥
- `args`:命令行魔法参数
- **`asm`**:汇编和反汇编,支持 i386/i686/amd64/thumb 等
- `constants`:对不同架构和操作系统的常量的快速访问
- `config`:配置文件
- `context`:设置运行时变量
- **`dynelf`**:用于远程函数泄露
- `encoders`:对 shellcode 进行编码
- **`elf`**:用于操作 ELF 可执行文件和库
- `flag`:提交 flag 到服务器
- **`fmtstr`**:格式化字符串利用工具
- **`gdb`**:与 gdb 配合使用
- `libcdb`libc 数据库
- `log`:日志记录
- **`memleak`**:用于内存泄露
- **`rop`**ROP 利用模块,包括 rop 和 srop
- `runner`:运行 shellcode
- **`shellcraft`**shellcode 生成器
- `term`:终端处理
- `timeout`:超时处理
- **`tubes`**:能与 sockets, processes, ssh 等进行连接
- `ui`:与用户交互
- `useragents`useragent 字符串数据库
- **`util`**:一些实用小工具
## 使用 Pwntools
下面我们对常用模块和函数做详细的介绍。
### tubes
在一次漏洞利用中,首先当然要与二进制文件或者目标服务器进行交互,这就要用到 tubes 模块。
主要函数在 `pwnlib.tubes.tube` 中实现,子模块只实现某管道特殊的地方。四种管道和相对应的子模块如下:
- `pwnlib.tubes.process`:进程
- `>>> p = process('/bin/sh')`
- `pwnlib.tubes.serialtube`:串口
- `pwnlib.tubes.sock`:套接字
- `>>> r = remote('127.0.0.1', 1080)`
- `>>> l = listen(1080)`
- `pwnlib.tubes.ssh`SSH
- `>>> s = ssh(host='example.com`, user='name', password='passwd')`
`pwnlib.tubes.tube` 中的主要函数:
- `interactive()`:可同时读写管道,相当于回到 shell 模式进行交互,在取得 shell 之后调用
- `recv(numb=1096, timeout=default)`:接收指定字节数的数据
- `recvall()`:接收数据直到 EOF
- `recvline(keepends=True)`:接收一行,可选择是否保留行尾的 `\n`
- `recvrepeat(timeout=default)`:接收数据直到 EOF 或 timeout
- `recvuntil(delims, timeout=default)`:接收数据直到 delims 出现
- `send(data)`:发送数据
- `sendline(data)`:发送一行,默认在行尾加 `\n`
- `close()`:关闭管道
下面是一个例子,先使用 listen 开启一个本地的监听端口,然后使用 remote 开启一个套接字管道与之交互:
```text
>>> from pwn import *
>>> l = listen()
[x] Trying to bind to 0.0.0.0 on port 0
[x] Trying to bind to 0.0.0.0 on port 0: Trying 0.0.0.0
[+] Trying to bind to 0.0.0.0 on port 0: Done
[x] Waiting for connections on 0.0.0.0:46147
>>> r = remote('localhost', l.lport)
[x] Opening connection to localhost on port 46147
[x] Opening connection to localhost on port 46147: Trying ::1
[x] Opening connection to localhost on port 46147: Trying 127.0.0.1
[+] Opening connection to localhost on port 46147: Done
>>> [+] Waiting for connections on 0.0.0.0:46147: Got connection from 127.0.0.1 on port 38684
>>> c = l.wait_for_connection()
>>> r.send('hello\n')
>>> c.recv()
'hello\n'
>>> r.send('hello\n')
>>> c.recvline()
'hello\n'
>>> r.sendline('hello')
>>> c.recv()
'hello\n'
>>> r.sendline('hello')
>>> c.recvline()
'hello\n'
>>> r.sendline('hello')
>>> c.recvline(keepends=False)
'hello'
>>> r.send('hello world')
>>> c.recvuntil('hello')
'hello'
>>> c.recv()
' world'
>>> c.close()
[*] Closed connection to 127.0.0.1 port 38684
>>> r.close()
[*] Closed connection to localhost port 46147
```
下面是一个与进程交互的例子:
```text
>>> p = process('/bin/sh')
[x] Starting local process '/bin/sh'
[+] Starting local process '/bin/sh': pid 26481
>>> p.sendline('sleep 3; echo hello world;')
>>> p.recvline(timeout=1)
'hello world\n'
>>> p.sendline('sleep 3; echo hello world;')
>>> p.recvline(timeout=1)
''
>>> p.recvline(timeout=5)
'hello world\n'
>>> p.interactive()
[*] Switching to interactive mode
whoami
firmy
^C[*] Interrupted
>>> p.close()
[*] Stopped process '/bin/sh' (pid 26481)
```
### shellcraft
使用 shellcraft 模块可以生成对应架构和 shellcode 代码,直接使用链式调用的方法就可以得到,首先指定体系结构,再指定操作系统:
```text
>>> print shellcraft.i386.nop().strip('\n')
nop
>>> print shellcraft.i386.linux.sh()
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
/* push '/bin///sh\x00' */
push 0x68
push 0x732f2f2f
push 0x6e69622f
mov ebx, esp
/* push argument array ['sh\x00'] */
/* push 'sh\x00\x00' */
push 0x1010101
xor dword ptr [esp], 0x1016972
xor ecx, ecx
push ecx /* null terminate */
push 4
pop ecx
add ecx, esp
push ecx /* 'sh\x00' */
mov ecx, esp
xor edx, edx
/* call execve() */
push SYS_execve /* 0xb */
pop eax
int 0x80
```
### asm
该模块用于汇编和反汇编代码。
体系结构,端序和字长需要在 `asm()``disasm()` 中设置,但为了避免重复,运行时变量最好使用 `pwnlib.context` 来设置。
汇编:(`pwnlib.asm.asm`)
```text
>>> asm('nop')
'\x90'
>>> asm(shellcraft.nop())
'\x90'
>>> asm('nop', arch='arm')
'\x00\xf0 \xe3'
>>> context.arch = 'arm'
>>> context.os = 'linux'
>>> context.endian = 'little'
>>> context.word_size = 32
>>> context
ContextType(arch = 'arm', bits = 32, endian = 'little', os = 'linux')
>>> asm('nop')
'\x00\xf0 \xe3'
```
```text
>>> asm('mov eax, 1')
'\xb8\x01\x00\x00\x00'
>>> asm('mov eax, 1').encode('hex')
'b801000000'
```
请注意,这里我们生成了 i386 和 arm 两种不同体系结构的 `nop`,当你使用不同与本机平台的汇编时,需要安装该平台的 binutils方法在上面已经介绍过了。
反汇编:(`pwnlib.asm.disasm`)
```text
>>> print disasm('\xb8\x01\x00\x00\x00')
0: b8 01 00 00 00 mov eax,0x1
>>> print disasm('6a0258cd80ebf9'.decode('hex'))
0: 6a 02 push 0x2
2: 58 pop eax
3: cd 80 int 0x80
5: eb f9 jmp 0x0
```
构建具有指定二进制数据的 ELF 文件:(`pwnlib.asm.make_elf`)
```text
>>> context.clear(arch='amd64')
>>> context
ContextType(arch = 'amd64', bits = 64, endian = 'little')
>>> bin_sh = asm(shellcraft.amd64.linux.sh())
>>> bin_sh
'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'
>>> filename = make_elf(bin_sh, extract=False)
>>> filename
'/tmp/pwn-asm-V4GWGN/step3-elf'
>>> p = process(filename)
[x] Starting local process '/tmp/pwn-asm-V4GWGN/step3-elf'
[+] Starting local process '/tmp/pwn-asm-V4GWGN/step3-elf': pid 28323
>>> p.sendline('echo hello')
>>> p.recv()
'hello\n'
```
这里我们生成了 amd64即 64 位 `/bin/sh` 的 shellcode配合上 asm 函数,即可通过 `make_elf` 得到 ELF 文件。
另一个函数 `pwnlib.asm.make_elf_from_assembly` 允许你构建具有指定汇编代码的 ELF 文件:
```text
>>> asm_sh = shellcraft.amd64.linux.sh()
>>> print asm_sh
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
/* push '/bin///sh\x00' */
push 0x68
mov rax, 0x732f2f2f6e69622f
push rax
mov rdi, rsp
/* push argument array ['sh\x00'] */
/* push 'sh\x00' */
push 0x1010101 ^ 0x6873
xor dword ptr [rsp], 0x1010101
xor esi, esi /* 0 */
push rsi /* null terminate */
push 8
pop rsi
add rsi, rsp
push rsi /* 'sh\x00' */
mov rsi, rsp
xor edx, edx /* 0 */
/* call execve() */
push SYS_execve /* 0x3b */
pop rax
syscall
>>> filename = make_elf_from_assembly(asm_sh)
>>> filename
'/tmp/pwn-asm-ApZ4_p/step3'
>>> p = process(filename)
[x] Starting local process '/tmp/pwn-asm-ApZ4_p/step3'
[+] Starting local process '/tmp/pwn-asm-ApZ4_p/step3': pid 28429
>>> p.sendline('echo hello')
>>> p.recv()
'hello\n'
```
与上一个函数不同的是,`make_elf_from_assembly` 直接从汇编生成 ELF 文件,并且保留了所有的符号,例如标签和局部变量等。
### elf
该模块用于 ELF 二进制文件的操作,包括符号查找、虚拟内存、文件偏移,以及修改和保存二进制文件等功能。(`pwnlib.elf.elf.ELF`)
```text
>>> e = ELF('/bin/cat')
[*] '/bin/cat'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
>>> print hex(e.address)
0x400000
>>> print hex(e.symbols['write'])
0x401680
>>> print hex(e.got['write'])
0x60b070
>>> print hex(e.plt['write'])
0x401680
```
上面的代码分别获得了 ELF 文件装载的基地址、函数地址、GOT 表地址和 PLT 表地址。
我们常常用它打开一个 libc.so从而得到 system 函数的位置,这在 CTF 中是非常有用的:
```text
>>> e = ELF('/usr/lib/libc.so.6')
[*] '/usr/lib/libc.so.6'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
>>> print hex(e.symbols['system'])
0x42010
```
我们甚至可以修改 ELF 文件的代码:
```text
>>> e = ELF('/bin/cat')
>>> e.read(e.address+1, 3)
'ELF'
>>> e.asm(e.address, 'ret')
>>> e.save('/tmp/quiet-cat')
>>> disasm(file('/tmp/quiet-cat','rb').read(1))
' 0: c3 ret'
```
下面是一些常用函数:
- `asm(address, assembly)`:汇编指定指令并插入到 ELF 的指定地址处,需要使用 ELF.save() 保存
- `bss(offset)`:返回 `.bss` 段加上 `offset` 后的地址
- `checksec()`:打印出文件使用的安全保护
- `disable_nx()`:关闭 NX
- `disasm(address, n_bytes)`:返回对指定虚拟地址进行反汇编后的字符串
- `offset_to_vaddr(offset)`:将指定偏移转换为虚拟地址
- `vaddr_to_offset(address)`:将指定虚拟地址转换为文件偏移
- `read(address, count)`:从指定虚拟地址读取 `count` 个字节的数据
- `write(address, data)`:在指定虚拟地址处写入 `data`
- `section(name)`:获取 `name` 段的数据
- `debug()`:使用 `gdb.debug()` 进行调试
最后还要注意一下 `pwnlib.elf.corefile`它用于处理核心转储文件Core Dump当我们在写利用代码时核心转储文件是非常有用的关于它更详细的内容已经在前面 Linux基础一章中讲过这里我们还是使用那一章中的示例代码但使用 pwntools 来操作。
```text
>>> core = Corefile('/tmp/core-a.out-30555-1507796886')
[x] Parsing corefile...
[*] '/tmp/core-a.out-30555-1507796886'
Arch: i386-32-little
EIP: 0x565cd57b
ESP: 0x4141413d
Exe: '/home/firmy/a.out' (0x565cd000)
Fault: 0x4141413d
[+] Parsing corefile...: Done
>>> core.registers
{'xds': 43, 'eip': 1448924539, 'xss': 43, 'esp': 1094795581, 'xgs': 99, 'edi': 0, 'orig_eax': 4294967295, 'xcs': 35, 'eax': 1, 'ebp': 1094795585, 'xes': 43, 'eflags': 66182, 'edx': 4151195744, 'ebx': 1094795585, 'xfs': 0, 'esi': 4151189032, 'ecx': 1094795585}
>>> print core.maps
565cd000-565ce000 r-xp 1000 /home/firmy/a.out
565ce000-565cf000 r--p 1000 /home/firmy/a.out
565cf000-565d0000 rw-p 1000 /home/firmy/a.out
57b3c000-57b5e000 rw-p 22000
f7510000-f76df000 r-xp 1cf000 /usr/lib32/libc-2.26.so
f76df000-f76e0000 ---p 1000 /usr/lib32/libc-2.26.so
f76e0000-f76e2000 r--p 2000 /usr/lib32/libc-2.26.so
f76e2000-f76e3000 rw-p 1000 /usr/lib32/libc-2.26.so
f76e3000-f76e6000 rw-p 3000
f7722000-f7724000 rw-p 2000
f7724000-f7726000 r--p 2000 [vvar]
f7726000-f7728000 r-xp 2000 [vdso]
f7728000-f774d000 r-xp 25000 /usr/lib32/ld-2.26.so
f774d000-f774e000 r--p 1000 /usr/lib32/ld-2.26.so
f774e000-f774f000 rw-p 1000 /usr/lib32/ld-2.26.so
ffe37000-ffe58000 rw-p 21000 [stack]
>>> print hex(core.fault_addr)
0x4141413d
>>> print hex(core.pc)
0x565cd57b
>>> print core.libc
f7510000-f76df000 r-xp 1cf000 /usr/lib32/libc-2.26.so
```
### dynelf
`pwnlib.dynelf.DynELF`
该模块是专门用来应对无 libc 情况下的漏洞利用。它首先找到 glibc 的基地址,然后使用符号表和字符串表对所有符号进行解析,直到找到我们需要的函数的符号。这是一个有趣的话题,我们会专门开一个章节去讲解它。详见 *4.4 使用 DynELF 泄露函数地址*
### fmtstr
`pwnlib.fmtstr.FmtStr``pwnlib.fmtstr.fmtstr_payload`
该模块用于格式化字符串漏洞的利用,格式化字符串漏洞是 CTF 中一种常见的题型,我们会在后面的章节中详细讲述,关于该模块的使用也会留到那儿。详见 *3.3.1 格式化字符串漏洞*
### gdb
`pwnlib.gdb`
在写漏洞利用的时候,常常需要使用 gdb 动态调试,该模块就提供了这方面的支持。
两个常用函数:
- `gdb.attach(target, gdbscript=None)`:在一个新终端打开 gdb 并 attach 到指定 PID 的进程,或是一个 `pwnlib.tubes` 对象。
- `gdb.debug(args, gdbscript=None)`:在新终端中使用 gdb 加载一个二进制文件。
上面两种方法都可以在开启的时候传递一个脚本到 gdb可以很方便地做一些操作如自动设置断点。
```python
# attach to pid 1234
gdb.attach(1234)
# attach to a process
bash = process('bash')
gdb.attach(bash, '''
set follow-fork-mode child
continue
''')
bash.sendline('whoami')
```
```text
# Create a new process, and stop it at 'main'
io = gdb.debug('bash', '''
# Wait until we hit the main executable's entry point
break _start
continue
# Now set breakpoint on shared library routines
break malloc
break free
continue
''')
```
### memleak
`pwnlib.memleak`
该模块用于内存泄露的利用。可用作装饰器。它会将泄露的内存缓存起来,在漏洞利用过程中可能会用到。
### rop
### util
`pwnlib.util.packing`, `pwnlib.util.cyclic`
util 其实是一些模块的集合包含了一些实用的小工具。这里主要介绍两个packing 和 cyclic。
packing 模块用于将整数打包和解包,它简化了标准库中的 `struct.pack``struct.unpack` 函数,同时增加了对任意宽度整数的支持。
使用 `p32`, `p64`, `u32`, `u64` 函数分别对 32 位和 64 位整数打包和解包,也可以使用 `pack()` 自己定义长度,另外添加参数 `endian``signed` 设置端序和是否带符号。
```text
>>> p32(0xdeadbeef)
'\xef\xbe\xad\xde'
>>> p64(0xdeadbeef).encode('hex')
'efbeadde00000000'
>>> p32(0xdeadbeef, endian='big', sign='unsigned')
'\xde\xad\xbe\xef'
```
```text
>>> u32('1234')
875770417
>>> u32('1234', endian='big', sign='signed')
825373492
>>> u32('\xef\xbe\xad\xde')
3735928559
```
cyclic 模块在缓冲区溢出中很有用,它帮助生成模式字符串,然后查找偏移,以确定返回地址。
```text
>>> cyclic(20)
'aaaabaaacaaadaaaeaaa'
>>> cyclic_find(0x61616162)
4
```
## Pwntools 在 CTF 中的运用
可以在下面的仓库中找到大量使用 pwntools 的 write-up
[pwntools-write-ups](https://github.com/Gallopsled/pwntools-write-ups)
## 参考资料
- [docs.pwntools.com](https://docs.pwntools.com/en/stable/index.html)