概述
Pwntools是一个CTF框架和漏洞利用开发库.它使用Python编写,旨在实现快速原型设计和开发,并尽可能简化漏洞利用程序的编写.
通过下述命令进行安装:
1
pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple pwntools
管道(Tubes)
Tubes是用于与进程进行通信的类,通常用于传输数据、发送或接收信息.
pwntools提供了以下几种不同的Tube类型来处理不同的情况:
process
:表示一个本地进程,你可以通过管道与它进行交互.
remote
:表示与远程主机上程序的链接,你可以通过网络与远程程序进行交互.
listen
:允许你在本地监听一个端口,等待远程连接.
socket
:表示一个原始的网络套接字,允许更细粒度的网络交互.
程序连接
语句
含义
io = process(“本地文件路径”)
本地连接
io = remote(“本地文件路径”)
远程连接
发送数据
语句
含义
io.send(payload)
发送payload
io.sendline(payload)
发送payload,追加’\n’,进行换行
io.sendafter(str, payload)
接收到str后,发送payload
io.sendlineafter(str, payload)
接收到str后,发送payload,并进行换行
接收数据
语句
含义
io.recv(N)
接收N个字符
io.recvline()
接收数据,直到遇到换行符
io.recvuntil(delim)
接收数据,直到遇到分隔符
io.recvregex(pattern)
接收数据,直至满足一个regex模式
io.recvrepeat(timeout)
接收数据,直到出现超时
io.clean()
丢弃所有缓冲数据
io.interactive()
直接与程序进行输入输出交互
操作整数
语句
含义
io.pack(int)
发送一个字大小的打包整数
io.unpack()
接收并解压缩一个字大小的整数
示例代码
1
2
3
4
5
6
from pwn import *
context . log_level = 'debug'
io = process ( 'sh' )
io . sendline ( 'echo Hello, world' )
io . recvline ()
实用工具(Utility)
打包和解包整数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
print ( pack ( 1 ))
# '\x01\x00\x00\x00'
print ( pack ( - 1 ))
# '\xff\xff\xff\xff'
print ( pack ( 1 , endian = 'big' ))
# '\x00\x00\x00\x01'
print ( p16 ( 1 ))
# '\x01\x00'
print ( hex ( unpack ( b 'AAAA' )))
# '0x41414141'
print ( hex ( u16 ( b 'AA' )))
# '0x4141'
文件输入输出
1
2
3
4
5
6
7
from pwn import *
write ( 'luoFile' , 'data' )
print ( read ( 'luoFile' ))
# 'data'
print ( read ( 'luoFile' , 1 ))
# 'd'
编码和解码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import *
#Base64
print ( b64d ( b64e ( b 'luo pwntools' )))
# b'luo pwntools'
#URL编码
print ( urlencode ( "luo pwntools" ))
# %6c%75%6f%20%70%77%6e%74%6f%6f%6c%73
#16进制编码
print ( enhex ( b 'luo pwntools' ))
# '68656c6c6f'
print ( unhex ( '6c756f2070776e746f6f6c73' ))
# b'luo pwntools'
#位操作
print ( bits ( b 'A' )) #'A' ASCII编码为65,二进制表示为01000001
# [0, 1, 0, 0, 0, 0, 0, 1]
print ( unbits ([ 0 , 1 , 0 , 0 , 0 , 0 , 0 , 1 ]))
# 'A'
#16进制查看
print ( hexdump ( b 'luo pwntools' , 16 ))
# 00000000 6c 75 6f 20 70 77 6e 74 6f 6f 6c 73 │luo │pwnt│ools│
cyclic
1
2
3
4
5
6
7
8
9
10
# 计算缓冲区到返回地址的距离
from pwn import *
buffer_size = 200
pattern = cyclic ( buffer_size )
print ( pattern )
crash_address = 0x61616167
offset = cyclic_find ( crash_address )
print ( f "offset: { offset } " )
上下文(Context)
context模块允许你设置目标系统的平台架构以及调试信息等相关配置.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
# 操作系统为Linux,设置架构为64 位,
context . os = 'linux'
context . arch = 'amd64'
# 设置字节序为小端
context . endian = 'little'
# 设置日志级别为 debug
context . log_level = 'debug'
# 一次设置多值
context ( os = 'linux' , arch = 'amd64' , log_level = 'debug' )
ELF
加载ELF
1
2
3
4
5
6
7
8
9
10
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
使用符号
ELF.symbols
:列出所有已知符号.plt优先于got
ELF.got
:只包含got
ELF.plt
:只包含plt
ELF.functions
:只包含函数(需要DWARF符号)
示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 )
# 输出如下:
[ * ] '/bin/bash'
Arch : amd64 - 64 - little
RELRO : Full RELRO
Stack : Canary found
NX : NX enabled
PIE : PIE enabled
FORTIFY : Enabled
0x130cc0 -> license
0x2f610 -> execve
0x133af0 -> got . execve
0x2f610 -> plt . execve
0x60e80 -> list_all_jobs
修改基地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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' ])
# 输出如下:
[ * ] '/bin/bash'
Arch : amd64 - 64 - little
RELRO : Full RELRO
Stack : Canary found
NX : NX enabled
PIE : PIE enabled
FORTIFY : Enabled
0x0 -> base address
0x31760 -> entry point
0x2f610 -> execve
--------------------------
0x12340000 -> base address
0x12371760 -> entry point
0x1236f610 -> execve
读取ELF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 ))
# 输出如下:
[ * ] '/bin/bash'
Arch : amd64 - 64 - little
RELRO : Full RELRO
Stack : Canary found
NX : NX enabled
PIE : PIE enabled
FORTIFY : Enabled
b ' \x7f ELF'
0x130cc0 -> 0xfd7e0
b 'License GPLv3+'
2 fee0 : f3 0 f 1 e fa endbr64
2 fee4 : 41 57 push r15
2 fee6 : 41 56 push r14
2 fee8 : 41 55 push r13
2 feea : 41 54 push r12
修补ELF
1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
e = ELF ( '/bin/bash' )
# Cause a debug break on the 'exit' command
e . asm ( e . symbols [ 'exit_builtin' ], 'int3' )
# Change the license
p_license = e . symbols [ 'bash_license' ]
license = e . unpack ( p_license )
e . write ( license , b 'Hello, world! \n\x00 ' )
e . save ( './bash-modified' )
我们使用下述命令来运行修改后的bash程序.
1
2
3
4
5
$ chmod +x ./bash-modified
$ ./bash-modified -c 'exit'
Trace/breakpoint trap
$ ./bash-modified --version | grep "Hello"
Hello, world!
搜索ELF
1
2
3
4
5
6
7
8
9
10
from pwn import *
e = ELF ( '/bin/bash' )
for address in e . search ( '/bin/sh \x00 ' ):
print hex ( address )
# 输出如下:
0x31e62
0x31f01
构建ELF
1
2
3
4
5
from pwn import *
ELF . from_bytes ( b ' \xcc ' ) . save ( './int3-1' )
ELF . from_assembly ( 'int3' ) . save ( './int3-2' )
ELF . from_assembly ( 'nop' , arch = 'powerpc' ) . save ( './powerpc-nop' )
运行和调试ELF
1
2
3
4
5
6
7
# 等同于 io = process('./luoPwn')
e = ELF ( './luoPwn' )
io = e . process ()
# 等同于 io = gdb.debug('./luoPwn')
e = ELF ( '/luoPwn' )
io = e . debug ()
汇编
Pwntools让你在任何架构中进行汇编变的非常容易,并提供了多种可定制的ShellCode.
基础汇编
1
2
3
4
5
6
7
from pwn import *
print ( repr ( asm ( 'xor edi, edi' )))
# '1\xff'
print ( enhex ( asm ( 'xor edi, edi' )))
# 31ff
汇编模版
shellcraft 模块会提供一些现成的汇编代码.
如下生成一段简单的ShellCode,通过系统调用(execve)启动一个 /bin/sh shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from pwn import *
#help(shellcraft.sh)
print ( '---' )
print ( shellcraft . sh ())
print ( '---' )
print ( enhex ( asm ( shellcraft . sh ())))
# 输出如下:
---
/* execve ( path = '/bin///sh' , argv = [ 'sh' ], envp = 0 ) */
/* push b '/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
---
6 a68682f2f2f73682f62696e89e368010101018134247269010131c9516a045901e15189e131d26a0b58cd80
命令行工具
asm
:将汇编转换为机器码.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ asm nop
90
$ asm nop | xxd
00000000: 90
# 从stdin上获取数据
$ echo 'push ebx; pop edi' | asm
535f
# 通过--format选项指定输出格式.支持的参数有raw、hex 、string 和 elf
$ asm --format= string 'int3'
b'\xcc'
$ asm --format= hex 'int3'
cc
# 生成arm架构下的汇编
$ asm --context= arm 'mov r0, r1'
0100a0e1
disasm
:将机器码反汇编为汇编指令.
1
2
3
4
5
$ disasm cd80
0: cd 80 int 0x80
$ asm nop | disasm
0: 90 nop
shellcraft
:是内部shellcraft模块的命令行接口.在命令行中,必须按arch.os.template的顺序指定完整的环境信息.
1
2
3
4
5
6
$ shellcraft i386.linux.sh
6a68682f2f2f73682f62696e89e368010101018134247269010131c9516a045901e15189e131d26a0b58cd80
#arm架构下汇编
$ shellcraft arm.linux.sh
687000e3417144e304702de52f7f02e32f7347e304702de52f7206e3697e46e304702de50d00a0e1737806e304702de50cc02ce004c02de50410a0e30d1081e001c0a0e104c02de50d10a0e1022022e00b70a0e3000000ef
异构架构
为其他平台进行汇编交互,需要安装适当版本的binutils
.可参考installing.md .
1
2
# 使用下述命令进行全平台binutils安装
apt install binutils-*
编译arm架构下的汇编如下:
1
2
3
4
5
6
7
8
from pwn import *
context . arch = 'arm'
print ( repr ( asm ( 'mov r0, r1' )))
# '\x01\x00\xa0\xe1'
print ( enhex ( asm ( 'mov r0, r1' )))
# 0100a0e1
调试
需要先进行gdb安装,如下:
1
apt install gdb gdbserver
GDB启动进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
context . log_level = 'debug'
# 指定GDB调试终端,需要先运行 tmux
# 进行tmux窗口后, 按下Ctrl + D, 即可取消窗口分割
context . terminal = [ 'tmux' , 'splitw' ]
gdb_commands = f '''
b exit
b _exit
c
'''
io = gdb . debug ( "/bin/bash" , gdb_commands )
io . sendline ( 'echo hello' )
io . recvline ()
io . interactive ()
GDB附加进程
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
#context.log_level = 'debug'
io = process ( '/bin/sh' )
gdb_commands = f '''
b exit
b _exit
c
'''
gdb . attach ( io , gdb_commands )
io . interactive ()
ROP
ROP全称为Return-oriented Programming(面向返回的编程,是一种绕过NX(禁止执行,也称为数据执行防护(DEP))的技术.
加载ELF
1
2
3
4
5
6
7
8
9
10
from pwn import *
#context.arch = 'amd64'
context . binary = elf = ELF ( '/bin/sh' )
rop = ROP ( elf )
print ( rop . rbx )
# 输出如下:
# Gadget地址 反汇编内容 加载的寄存器 堆栈调整大小
Gadget ( 0x4a62 , [ 'pop rbx' , 'ret' ], [ 'rbx' ], 0x10 )
修复地址
1
2
3
4
5
6
7
8
9
10
from pwn import *
#context.arch = 'amd64'
context . binary = elf = ELF ( '/bin/sh' )
elf . address = 0xff000000
rop = ROP ( elf )
print ( rop . rbx )
# 输出如下:
Gadget ( 0xff004a62 , [ 'pop rbx' , 'ret' ], [ 'rbx' ], 0x10 )
检查Gadgets
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
#context.arch = 'amd64'
context . binary = elf = ELF ( '/bin/sh' )
gadgets = ROP ( elf ) . gadgets
for addr , insns in gadgets . items ():
print ( insns )
# 输出如下:
Gadget ( 0x784b , [ 'add esp, 0x10' , 'pop rbx' , 'pop rbp' , 'pop r12' , 'ret' ], [ 16 , 'rbx' , 'rbp' , 'r12' ], 0x30 )
Gadget ( 0xc55e , [ 'add esp, 0x10' , 'pop rbx' , 'ret' ], [ 16 , 'rbx' ], 0x20 )
Gadget ( 0x106ec , [ 'add esp, 0x130' , 'pop rbx' , 'ret' ], [ 304 , 'rbx' ], 0x140 )
Gadget ( 0x14060 , [ 'add esp, 0x18' , 'ret' ], [ 24 ], 0x20 )
Gadget ( 0x64da , [ 'add esp, 0x20' , 'pop rbx' , 'pop rbp' , 'pop r12' , 'ret' ], [ 32 , 'rbx' , 'rbp' , 'r12' ], 0x40 )
如果上述结果中没有想要的,建议使用ROPgadget .
添加原始数据
1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
#context.arch = 'amd64'
context . binary = elf = ELF ( '/bin/sh' )
rop = ROP ( elf )
rop . raw ( 0xdeadbeef )
rop . raw ( 0xcafebabe )
rop . raw ( 'asdf' )
print ( rop . chain ())
# 输出如下:
b ' \xef\xbe\xad\xde\x00\x00\x00\x00\xbe\xba\xfe\xca\x00\x00\x00\x00 asdf'
查看ROP栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
#context.arch = 'amd64'
context . binary = elf = ELF ( '/bin/sh' )
rop = ROP ( elf )
rop . raw ( 0xdeadbeef )
rop . raw ( 0xcafebabe )
rop . raw ( 'asdf' )
print ( rop . dump ())
# 输出如下:
0x0000 : 0xdeadbeef
0x0008 : 0xcafebabe
0x0010 : b 'asdf' 'asdf'
提取原始字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
#context.arch = 'amd64'
context . binary = elf = ELF ( '/bin/sh' )
rop = ROP ( elf )
rop . raw ( 0xdeadbeef )
rop . raw ( 0xcafebabe )
rop . raw ( 'asdf' )
print ( hexdump ( bytes ( rop )))
# 输出如下
00000000 ef be ad de 00 00 00 00 be ba fe ca 00 00 00 00 │····│····│····│····│
00000010 61 73 64 66 │ asdf │
00000014
函数调用
注意设置目标架构.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
#context.arch = 'amd64'
context . binary = elf = ELF ( '/bin/sh' )
rop = ROP ( elf )
rop . call ( 0xdeadbeef , [ 0 , 1 ])
print ( rop . dump ())
# 输出如下:
0x0000 : 0x4b70 pop rdi ; ret
0x0008 : 0x0 [ arg0 ] rdi = 0
0x0010 : 0x4a02 pop rsi ; ret
0x0018 : 0x1 [ arg1 ] rsi = 1
0x0020 : 0xdeadbeef
按名称调用函数
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
#context.arch = 'amd64'
context . binary = elf = ELF ( '/bin/sh' )
rop = ROP ( elf )
rop . execve ( 0xdeadbeef )
print ( rop . dump ())
# 输出如下:
0x0000 : 0x4b70 pop rdi ; ret
0x0008 : 0xdeadbeef [ arg0 ] rdi = 3735928559
0x0010 : 0x42a0 execve
多ELF寻找Gadgets
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
#context.arch = 'amd64'
context . binary = elf = ELF ( '/bin/bash' )
libc = elf . libc
elf . address = 0xAA000000
libc . address = 0xBB000000
rop = ROP ([ elf , libc ])
print ( rop . rax )
print ( rop . rbx )
print ( rop . rcx )
print ( rop . rdx )
# 输出如下:
Gadget ( 0xaa0686c3 , [ 'pop rax' , 'ret' ], [ 'rax' ], 0x10 )
Gadget ( 0xaa031d88 , [ 'pop rbx' , 'ret' ], [ 'rbx' ], 0x10 )
Gadget ( 0xbb0e2d4e , [ 'pop rcx' , 'pop rbx' , 'ret' ], [ 'rcx' , 'rbx' ], 0x18 )
Gadget ( 0xaa030462 , [ 'pop rdx' , 'ret' ], [ 'rdx' ], 0x10 )
获取Shell
直接调用execve
函数,然后从内存中找到"/bin/sh\x00"
地址作为第一个参数.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from pwn import *
#context.arch = 'amd64'
context . binary = elf = ELF ( '/bin/bash' )
libc = elf . libc
elf . address = 0xAA000000
libc . address = 0xBB000000
rop = ROP ([ elf , libc ])
binsh = next ( libc . search ( b "/bin/sh \x00 " ))
print ( f "binsh: { hex ( binsh ) } " )
rop . execve ( binsh , 0 , 0 )
print ( rop . dump ())
print ( hexdump ( bytes ( rop )))
# 输出如下:
binsh : 0xbb1a7e43
0x0000 : 0xaa0368b9 pop rsi ; ret
0x0008 : 0x0 [ arg1 ] rsi = 0
0x0010 : 0xaa031c86 pop rdi ; ret
0x0018 : 0xbb1a7e43 [ arg0 ] rdi = 3139075651
0x0020 : 0xaa030462 pop rdx ; ret
0x0028 : 0x0 [ arg2 ] rdx = 0
0x0030 : 0xaa02f610 execve
00000000 b9 68 03 aa 00 00 00 00 00 00 00 00 00 00 00 00 │· h ··│····│····│····│
00000010 86 1 c 03 aa 00 00 00 00 43 7 e 1 a bb 00 00 00 00 │····│····│ C ~ ··│····│
00000020 62 04 03 aa 00 00 00 00 00 00 00 00 00 00 00 00 │ b ···│····│····│····│
00000030 10 f6 02 aa 00 00 00 00 │····│····│
日志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
info ( 'Info!' )
debug ( 'Debug!' )
warn ( 'Warning!' )
success ( 'SUCCESS' )
error ( 'ERROR!' )
# 输出如下:
# 默认日志级别为info,不输出debug内容
[ * ] Info !
[ ! ] Warning !
[ + ] SUCCESS
[ ERROR ] ERROR !
可通过命令行或者context.log_level
设置日志级别
1
2
3
4
5
6
7
8
9
10
11
12
# 命令行
python ./ exp . py DEBUG
# 默认log输出到stdout, 配置log_file输出到指定文件
from pwn import *
context . log_level = 'debug'
context . log_file = './log.txt'
io = process ( 'sh' )
#io = process('sh', level='debug')
io . sendline ( 'echo Hello, world' )
io . recvline ()
参考链接
pwntools Github
pwntools tutorial
pwntools docs