pwntools的使用

该板块会持续更新,学一点写一点

学习资料来源于:1

导入pwntools库

1
from pwn import*

设置基本信息

1
context(os='linux',arch='AMD64',log_level='debug')

os是靶机的操作系统类型

arch是题目的架构,一般是AMD64(64位)或i386(32位)

log_level是指日志输出等级,设置为debug可以在脚本运行时输出我们具体发送了什么信息,靶机反馈了什么信息。

连接远程靶机

1
io = remote("ip",port)#前者为靶机地址,后者为端口号
1
io = ssh(host='ip',user='用户名',port=端口号,password='密码')

本地调试

1
io = process("./pwn")

发送信息

1
2
3
4
5
6
7
8
p.send(payload) # 直接发送payload

p.sendline(payload) # 发送payload,但是结尾会有一个\n

p.sendafter("string", payload) # 接收到 string (这里说的string可以替换成任何信息) 之后会发送payload,但是如果没有接收到string,那么就会导致脚本一直卡在这里不动

p.sendlineafer("string", payload) # 接收到 string 之后会发送payload 并且在payload最后添加\n

接收信息

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
p.recv(int) #利用recv来接收返回的数据,并且可以控制接受到的字节数
比如:p.recv(7) => 系统输出'hello world' => 我们会接受到'hello w'

p.recvline('string') #设置一个标识符,接收标识符所在的那一行
比如:p.recvline('O.o')
#系统输出:
Hello World
This is a test.
O.o This is the target line.
Goodbye.
#我们接收:
O.o This is the target line.

p.recvlines(N) 接收 N 行输出
比如:p.recvlines(3)
#系统输出
Hello World
This is a test.
O.o This is the target line.
Goodbye.
#我们接收
Hello World
This is a test.
O.o This is the target line.

p.recvuntil('string') 可以指定接收到某一字符串的时候停止 ,还有第二个参数 drop,drop=True(默认为false) 表示丢弃设定的停止符号
比如:p.recvuntil('or')
#系统输出:
hello world
#我们接收:
hello wor
比如:a = io.recvuntil(']', drop=True)
就是一直获取到`]`符号出现就停止,并且不接收`]`符号

常用接收地址:

64位:

1
write_addrs=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))

32位:

1
2
puts_addrs=u32(io.recvuntil(b'\xf7')[-4:])
#puts_addrs=u32(io.recv()[0:4])

开启交互

1
2
p.interactive()
#接受信息并且在终端操作,程序拿到shell,然后就可以转接到linux终端上.

构造发送地址类型

p64/p32/u64/u32这类函数的作用

1
2
3
4
5
6
7
8
9
10
11
p64(int) 
p64(0xfaceb00c) => '\x0c\xb0\xce\xfa\x00\x00\x00\x00\x00'

u64(str)
u64('\x0c\xb0\xce\xfa\x00\x00\x00\x00') =>0xfaceb00c

p32(int)
p32(0xfaceb00c) => '\x0c\xb0\xce\xfa'

u32(str)
u32('\x0c\xb0\xce\xfa') => 0xfaceb00c

p64()这种类型用于将消息变成对应的进制流(因为原本程序里面的数据都是已经编译过的,所以打入的数据也需要是编译过的,所以需要使用p64()这类工具)
u64()这种类型用于泄露地址的时候将泄露的进制流变成对应的原本的样子,方便来辨认查找glibc版本

因为一般计算机都是小端程序,所以这两个函数都自带有将数据变成小端需要的样子,如果遇到大端程序可能需要额外注意

除了p32()这种转化方式还有,flat(),它可以将多个数据结构(如字符串、整数等)连接在一起,并将它们转换为二进制数据。通常用于构建复杂的ROP链的shellcode。flat 函数会将数据扁平化,将它们按照顺序连接在一起,不做任何其他处理。在提供的代码中,flat 被用于构建一个包含多个元素的列表,然后将它们连接起来形成一个二进制数据。

1
payload = flat([0x12345678, 'AAAA', 0xdeadbeef], word_size=4/8)

生成指定大小字符串

1
cyclic(100)

汇编于反汇编

pwntools提供了两个工具:
asm函数可以将汇编代码转为对应的二进制
disasm函数则相反可以将二进制转化为汇编代码

1
2
3
4
5
>>> asm('mov eax, 0')   #汇编
'\xb8\x00\x00\x00\x00'

>>> disasm('\xb8\x0b\x00\x00\x00') #反汇编
'mov eax,0xb'

生成shellcode后门

注意对于64位程序使用该函数前需指定架构,默认架构为32位

1
2
3
4
5
6
7
context(os='linux', arch='i386')
# 表示将当前执行上下文的体系结构设置为i386(这里的i386可以通过checksec来查看文件是什么架构的
shellcode = asm(shellcraft.sh())
# asm()是把括号内的内容编译成机器码(只有机器码才可以执行),一般用来打入后门。pwntools自带的后门函数,可以生成类似system('/bin/sh/')这样功能的汇编代码
# 通常可以配合 .ljust() 来使用
shellcode.ljust(112, b'A')
# 这里的 .ljust() 是 Python 中字符串对象的方法,用于在字符串的右侧填充指定的字符,使字符串达到指定的长度。

纯净版shellcode

1
2
3
4
from pwn import *
context(arch='i386', os='linux')
shellcode = asm(shellcraft.sh())
shellcode.ljust(112, b'A')

专门收集shellcode的网站:https://www.exploit-db.com/shellcodes/43550

已编译好的shellcode:

64位linux的24Byte(字节)的shellcode

1
shellcode_x64 ="\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"

64位linux的23Byte的shellcode

1
shellcode_x64 ="\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"

很多时候我们需要自己手动编写shellcode来绕过一些检测

运行时使用gdb调试(还需要进一步学习)

gdb.attach

1
2
3
4
gdb.attach(p, gdbscript=""" b main; commands; silent printf "Breakpoint hit\n"; continue; end """)
在需要进行调试的位置插入gdb.attach(p)即可在执行到的时候打开gdb进行调试
p是指定的需要调试的进程(必须要本地调试,否则会报错)
gdbscript是打开gdb后需要进行的操作,使用 ; 进行隔离

一般gdb.attach(p)可以和pause()函数连用,可以确保在gdb完全打开之前脚本不运行
pause()函数用于暂停脚本的运行,直到用户输入任意数据

ELF模块

我们可以通过这个模块来快速获取pwn文件的got表地址以及plt表地址
用于获取ELF文件的信息,首先使用ELF()获取这个文件的句柄,然后使用这个句柄调用函数,和IO模块很相似。
下面演示了:获取基地址、获取函数地址(基于符号)、获取函数got地址、获取函数plt地址,和LibcSearcher库联动使用

1
2
3
4
5
6
7
8
9
10
11
elf = ELF('./pwn')

elf.address # 文件装载的基地址 => 0x400000

main_addr = elf.symbols['main'] # 获取函数地址 => 0x401680

write_got = elf.got['write'] # 获取对应函数在GOT表的地址 => 0x60b070

write_plt = elf.plt['write'] # 获取对应函数在PLT表的地址 => 0x401680

libc = LibcSearcher("puts",puts)#需要导入LibcSearcher库来匹配对应glibc版本