pwntools 使用

  1. 安装
    安装可以参考我写的另一篇文章,不过也就几条命令。

  2. 模块介绍
    使用 from pwn import * 将所有的模块导入到当前 namespace,这条语句还会帮你把 os,sys 等常用的系统库导入。

常用模块如下:

1
2
3
4
5
6
7
8
asm : 汇编与反汇编,支持x86/x64/arm/mips/powerpc等基本上所有的主流平台
dynelf : 用于远程符号泄漏,需要提供leak方法
elf : 对elf文件进行操作
gdb : 配合gdb进行调试
memleak : 用于内存泄漏
shellcraft : shellcode的生成器
tubes : 包括tubes.sock, tubes.process, tubes.ssh, tubes.serialtube,分别适用于不同场景的PIPE
utils : 一些实用的小功能,例如CRC计算,cyclic pattern等
  1. 连接

    1
    2
    3
    本地 :sh = process("./level0")
    远程:sh = remote("127.0.0.1",10001)
    关闭连接:sh.close()
  2. IO 模块

    1
    2
    3
    4
    5
    6
    7
    8
    sh.send(data)  发送数据
    sh.sendline(data) 发送一行数据,相当于在数据后面加\n
    sh.recv(numb = 2048, timeout = dufault) 接受数据,numb指定接收的字节,timeout指定超时
    sh.recvline(keepends=True) 接受一行数据,keepends为是否保留行尾的\n
    sh.recvuntil("Hello,World\n",drop=fasle) 接受数据直到我们设置的标志出现
    sh.recvall() 一直接收直到EOF
    sh.recvrepeat(timeout = default) 持续接受直到EOF或timeout
    sh.interactive() 直接进行交互,相当于回到shell的模式,在取得shell之后使用
  3. 汇编和反汇编
    汇编:

    1
    2
    3
    4
    >>> asm('nop')
    '\x90'
    >>> asm('nop', arch='arm')
    '\x00\xf0 \xe3'

可以使用 context 来指定 cpu 类型以及操作系统

1
2
3
4
>>> context.arch      = 'i386'
>>> context.os = 'linux'
>>> context.endian = 'little'
>>> context.word_size = 32

使用 disasm 进行反汇编

1
2
3
4
5
>>> print disasm('6a0258cd80ebf9'.decode('hex'))
0: 6a 02 push 0x2
2: 58 pop eax
3: cd 80 int 0x80
5: eb f9 jmp 0x0

注意,asm 需要 binutils 中的 as 工具辅助,如果是不同于本机平台的其他平台的汇编,例如在我的 x86 机器上进行 mips 的汇编就会出现 as 工具未找到的情况,这时候需要安装其他平台的 cross-binutils。

  1. Shellcode 生成器
    1
    2
    3
    4
    5
    6
    7
    8
    >>> print shellcraft.i386.nop().strip('\n')
    nop
    >>> print shellcraft.i386.linux.sh()
    /* push '/bin///sh\x00' */
    push 0x68
    push 0x732f2f2f
    push 0x6e69622f
    ...

结合 asm 可以可以得到最终的 pyaload。

1
2
3
4
5
6
7
8
from pwn import *
context(os='linux',arch='amd64')
shellcode = asm(shellcraft.sh())

或者

from pwn import *
shellcode = asm(shellcraft.amd64.linux.sh())

除了直接执行 sh 之外,还可以进行其它的一些常用操作例如提权、反向连接等等。

  1. ELF 文件操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> e = ELF('/bin/cat')
    >>> print hex(e.address) # 文件装载的基地址
    0x400000
    >>> print hex(e.symbols['write']) # 函数地址
    0x401680
    >>> print hex(e.got['write']) # GOT表的地址
    0x60b070
    >>> print hex(e.plt['write']) # PLT的地址
    0x401680
    >>> print hex(e.search('/bin/sh').next())# 字符串/bin/sh的地址
  2. 整数 pack 与数据 unpack
    pack:p32,p64
    unpack:u32,u64

    1
    2
    3
    4
    5
    from pwn import *
    elf = ELF('./level0')
    sys_addr = elf.symbols['system']
    payload = 'a' * (0x80 + 0x8) + p64(sys_addr)
    ...
  3. ROP 链生成器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    elf = ELF('ropasaurusrex')
    rop = ROP(elf)
    rop.read(0, elf.bss(0x80))
    rop.dump()
    # ['0x0000: 0x80482fc (read)',
    # '0x0004: 0xdeadbeef',
    # '0x0008: 0x0',
    # '0x000c: 0x80496a8']
    str(rop)
    # '\xfc\x82\x04\x08\xef\xbe\xad\xde\x00\x00\x00\x00\xa8\x96\x04\x08'

使用 ROP (elf) 来产生一个 rop 的对象,这时 rop 链还是空的,需要在其中添加函数。

因为 ROP 对象实现了 getattr 的功能,可以直接通过 func call 的形式来添加函数,rop.read (0, elf.bss (0x80)) 实际相当于 rop.call (‘read’, (0, elf.bss (0x80)))。
通过多次添加函数调用,最后使用 str 将整个 rop chain dump 出来就可以了。

call (resolvable, arguments=()) : 添加一个调用,resolvable 可以是一个符号,也可以是一个 int 型地址,注意后面的参数必须是元组否则会报错,即使只有一个参数也要写成元组的形式 (在后面加上一个逗号)
chain () : 返回当前的字节序列,即 payload
dump () : 直观地展示出当前的 rop chain
raw () : 在 rop chain 中加上一个整数或字符串
search (move=0, regs=None, order=’size’) : 按特定条件搜索 gadget
unresolve (value) : 给出一个地址,反解析出符号
LINK:
Exploit 利器 ——Pwntools
pwntools 官网

作者:ch3ckr
链接:https://www.jianshu.com/p/355e4badab50
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。