相信各位CTF pwners一定对pwntools熟悉得不能再熟悉,做一道题时写下的第一行代码很可能就是
from pwn import *
。很多pwners对于pwntools的了解可能仅限于远程交互、ELF文件简单分析这些最基础的功能,但pwntools真的只有这些功能吗?你真的会使用pwntools吗?
本文从pwntools官方文档入手,进行pwntools功能的探索。
文章目录
A. 控制台实用命令——commandline
pwntools提供了一系列实用的控制台命令工具,源码相对路径:/pwnlib/commandline。
1. asm
命令用法:
pwn asm [-h] [-f {raw,hex,string,elf}] [-o file] [-c context] [-v AVOID] [-n] [-z] [-d] [-e ENCODER] [-i INFILE] [-r] [line ...]
选项:
options:
-h, --help show this help message and exit
-f {raw,hex,string,elf}, --format {raw,hex,string,elf}
Output format (defaults to hex for ttys, otherwise raw)
-o file, --output file
Output file (defaults to stdout)
-c context, --context context
The os/architecture/endianness/bits the shellcode will run in (default: linux/i386), choose from: ['16', '32', '64', 'android', 'baremetal', 'cgc', 'freebsd', 'linux',
'windows', 'powerpc64', 'aarch64', 'powerpc', 'sparc64', 'mips64', 'msp430', 'alpha', 'amd64', 'riscv', 'sparc', 'thumb', 'cris', 'i386', 'ia64', 'm68k', 'mips', 's390',
'none', 'avr', 'arm', 'vax', 'little', 'big', 'be', 'eb', 'le', 'el']
-v AVOID, --avoid AVOID
Encode the shellcode to avoid the listed bytes (provided as hex)
-n, --newline Encode the shellcode to avoid newlines
-z, --zero Encode the shellcode to avoid NULL bytes
-d, --debug Debug the shellcode with GDB
-e ENCODER, --encoder ENCODER
Specific encoder to use
-i INFILE, --infile INFILE
Specify input file
-r, --run Run output
这里的asm命令有一个-z选项和-n选项,可以通过修改原本的指令来避免指令中出现换行和空字符。但是经过实际测试发现,这个功能转换出来的汇编指令并不是很好看。因此asm的这两个选项谨慎使用。
2. checksec
usage: pwn checksec [-h] [--file [elf ...]] [elf ...]
Check binary security settings
positional arguments:
elf Files to check
options:
-h, --help show this help message and exit
--file [elf ...] File to check (for compatibility with checksec.sh)
这个命令就不必多言了,检查ELF的安全选项,常用的命令。
3. constgrep
usage: pwn constgrep [-h] [-e] [-i] [-m] [-c arch_or_os] regex [constant]
Looking up constants from header files.
Example: constgrep -c freebsd -m ^PROT_ '3 + 4'
positional arguments:
regex The regex matching constant you want to find
constant The constant to find
options:
-h, --help show this help message and exit
-e, --exact Do an exact match for a constant instead of searching for a regex
-i, --case-insensitive
Search case insensitive
-m, --mask-mode Instead of searching for a specific constant value, search for values not containing strictly less bits that the given value.
-c arch_or_os, --context arch_or_os
The os/architecture/endianness/bits the shellcode will run in (default: linux/i386), choose from: ['16', '32', '64', 'android', 'baremetal', 'cgc', 'freebsd', 'linux',
'windows', 'powerpc64', 'aarch64', 'powerpc', 'sparc64', 'mips64', 'msp430', 'alpha', 'amd64', 'riscv', 'sparc', 'thumb', 'cris', 'i386', 'ia64', 'm68k', 'mips', 's390',
'none', 'avr', 'arm', 'vax', 'little', 'big', 'be', 'eb', 'le', 'el']
这个命令的功能有点意思。我们都知道在C语言标准库中有很多的宏定义常量,如mmap中的映射选项、读写权限选项等,有的时候,这种常量可能很多,在IDA中查看源码的时候,IDA当然是不可能会将函数传入的常数转换为这种宏定义的,这就会让我们阅读代码带来一定的困难,还需要去查阅源码才能知道传入常量的具体含义。而constgrep命令则允许我们通过一定的正则匹配筛选出源码中的常量,然后根据我们给出的值自动进行或操作拼接,最终给出原本的宏定义含义。如输入命令constgrep -c amd64 -m ^PROT_ 7,其输出如下:
#define PROT_NONE 0x0
#define PROT_READ 0x1
#define PROT_WRITE 0x2
#define PROT_EXEC 0x4
(PROT_WRITE | PROT_READ | PROT_EXEC) == 7
上面命令的功能是:找到amd64架构下所有以"PROT_"开头的宏定义,并通过或操作获得值7,输出中给出了或操作的具体项。如果不传入具体的数值而是添加-m选项,则会输出所有与正则匹配的宏定义。这个小工具有助于我们更容易理解IDA反编译出来的C代码。
4. cyclic
usage: pwn cyclic [-h] [-a alphabet] [-n length] [-c context] [-l lookup_value | count]
Cyclic pattern creator/finder
positional arguments:
count Number of characters to print
options:
-h, --help show this help message and exit
-a alphabet, --alphabet alphabet
The alphabet to use in the cyclic pattern (defaults to all lower case letters)
-n length, --length length
Size of the unique subsequences (defaults to 4).
-c context, --context context
The os/architecture/endianness/bits the shellcode will run in (default: linux/i386), choose from: ['16', '32', '64', 'android', 'baremetal', 'cgc', 'freebsd', 'linux',
'windows', 'powerpc64', 'aarch64', 'powerpc', 'sparc64', 'mips64', 'msp430', 'alpha', 'amd64', 'riscv', 'sparc', 'thumb', 'cris', 'i386', 'ia64', 'm68k', 'mips', 's390',
'none', 'avr', 'arm', 'vax', 'little', 'big', 'be', 'eb', 'le', 'el']
-l lookup_value, -o lookup_value, --offset lookup_value, --lookup lookup_value
Do a lookup instead printing the alphabet
这个工具可以生成指定长度的缓冲数据,可通过-a选项指定缓冲数据可以使用的字符。
5. debug
usage: pwn debug [-h] [-x GDBSCRIPT] [--pid PID] [-c context] [--exec EXECUTABLE] [--process PROCESS_NAME] [--sysroot SYSROOT]
Debug a binary in GDB
options:
-h, --help show this help message and exit
-x GDBSCRIPT Execute GDB commands from this file.
--pid PID PID to attach to
-c context, --context context
The os/architecture/endianness/bits the shellcode will run in (default: linux/i386), choose from: ['16', '32', '64', 'android', 'baremetal', 'cgc', 'freebsd', 'linux',
'windows', 'powerpc64', 'aarch64', 'powerpc', 'sparc64', 'mips64', 'msp430', 'alpha', 'amd64', 'riscv', 'sparc', 'thumb', 'cris', 'i386', 'ia64', 'm68k', 'mips', 's390',
'none', 'avr', 'arm', 'vax', 'little', 'big', 'be', 'eb', 'le', 'el']
--exec EXECUTABLE File to debug
--process PROCESS_NAME
Name of the process to attach to (e.g. "bash")
--sysroot SYSROOT GDB sysroot path
这个指令和gdb的功能类似,可以通过–pid参数对某个进程进行调试,可以通过–process参数根据进程名对进程进行调试等。
6. disablenx
后面跟上ELF文件名即可,可以让该ELF文件执行时关闭NX栈不可执行保护。
7. disasm
usage: pwn disasm [-h] [-c arch_or_os] [-a address] [--color] [--no-color] [hex ...]
Disassemble bytes into text format
positional arguments:
hex Hex-string to disassemble. If none are supplied, then it uses stdin in non-hex mode.
options:
-h, --help show this help message and exit
-c arch_or_os, --context arch_or_os
The os/architecture/endianness/bits the shellcode will run in (default: linux/i386), choose from: ['16', '32', '64', 'android', 'baremetal', 'cgc', 'freebsd', 'linux',
'windows', 'powerpc64', 'aarch64', 'powerpc', 'sparc64', 'mips64', 'msp430', 'alpha', 'amd64', 'riscv', 'sparc', 'thumb', 'cris', 'i386', 'ia64', 'm68k', 'mips', 's390',
'none', 'avr', 'arm', 'vax', 'little', 'big', 'be', 'eb', 'le', 'el']
-a address, --address address
Base address
--color Color output
--no-color Disable color output
反汇编命令,传入的数据是以16进制bytes的形式,如需要传入字符串’aa’则应该写成6161。
8. elfdiff
传入两个ELF文件,比较两个文件。
9. elfpatch
usage: pwn elfpatch [-h] elf offset bytes
Patch an ELF file
positional arguments:
elf File to patch
offset Offset to patch in virtual address (hex encoded)
bytes Bytes to patch (hex encoded)
options:
-h, --help show this help message and exit
修改ELF文件中的几个字节。
10. errno
传入一个整数或者errno的宏定义,获取其errno宏定义或值。
11. hex
将传入字符串转换为16进制字符串。
12. libcdb
libc相关信息查询。
pwn libcdb file [LIBC]:通过给定libc打印其哈希、关键函数偏移等
pwn libcdb lookup [FUNC] [OFFSET]:通过给定函数和偏移查询候选libc,推测是使用LibcSearcher引擎,较新的libc可能搜不到
pwn libcdb hash [HASH_VALUE]:通过给定哈希值获取libc,不常用
13. phd
与hexdump功能类似,以pretty形式打印二进制文件的每个字节。
14. pwnstrip
和strip功能类似,去除程序符号表增加逆向难度。
15. scramble
usage: pwn scramble [-h] [-f {raw,hex,string,elf}] [-o file] [-c context] [-p] [-v AVOID] [-n] [-z] [-d]
Shellcode encoder
options:
-h, --help show this help message and exit
-f {raw,hex,string,elf}, --format {raw,hex,string,elf}
Output format (defaults to hex for ttys, otherwise raw)
-o file, --output file
Output file (defaults to stdout)
-c context, --context context
The os/architecture/endianness/bits the shellcode will run in (default: linux/i386), choose from: ['16', '32', '64', 'android', 'baremetal', 'cgc', 'freebsd', 'linux',
'windows', 'powerpc64', 'aarch64', 'powerpc', 'sparc64', 'mips64', 'msp430', 'alpha', 'amd64', 'riscv', 'sparc', 'thumb', 'cris', 'i386', 'ia64', 'm68k', 'mips', 's390',
'none', 'avr', 'arm', 'vax', 'little', 'big', 'be', 'eb', 'le', 'el']
-p, --alphanumeric Encode the shellcode with an alphanumeric encoder
-v AVOID, --avoid AVOID
Encode the shellcode to avoid the listed bytes
-n, --newline Encode the shellcode to avoid newlines
-z, --zero Encode the shellcode to avoid NULL bytes
-d, --debug Debug the shellcode with GDB
这个命令看上去应该是一个shellcode的解码器,支持将shellcode解码成全字母,允许定义黑名单等,但是笔者在全网都没有搜到这个命令的一个使用的例子,试了各种方法命令运行都报错,甚至连new bing返回的结果都是错的。不过想要实现全字母shellcode还能用AE64。若有读者知道这个命令到底是怎么用的,还请不吝赐教。
16. shellcraft
usage: pwn shellcraft [-h] [-?] [-o file] [-f format] [-d] [-b] [-a] [-v AVOID] [-n] [-z] [-r] [--color] [--no-color] [--syscalls] [--address ADDRESS] [-l] [-s] [shellcode] [arg ...]
Microwave shellcode -- Easy, fast and delicious
positional arguments:
shellcode The shellcode you want
arg Argument to the chosen shellcode
options:
-h, --help show this help message and exit
-?, --show Show shellcode documentation
-o file, --out file Output file (default: stdout)
-f format, --format format
Output format (default: hex), choose from {e}lf, {r}aw, {s}tring, {c}-style array, {h}ex string, hex{i}i, {a}ssembly code, {p}reprocssed code, escape{d} hex string
-d, --debug Debug the shellcode with GDB
-b, --before Insert a debug trap before the code
-a, --after Insert a debug trap after the code
-v AVOID, --avoid AVOID
Encode the shellcode to avoid the listed bytes
-n, --newline Encode the shellcode to avoid newlines
-z, --zero Encode the shellcode to avoid NULL bytes
-r, --run Run output
--color Color output
--no-color Disable color output
--syscalls List syscalls
--address ADDRESS Load address
-l, --list List available shellcodes, optionally provide a filter
-s, --shared Generated ELF is a shared library
这个命令在命令行中的用法与代码中相似,其中有个实用的选项-l可以列出可用的shellcode,后面加上具体的架构可以获取更为详细的列表。不过笔者测试发现有的shellcode貌似找不到,谨慎使用。找不到的可以使用Metasploit这个更为强大的工具生成。
17. template
usage: pwn template [-h] [--host HOST] [--port PORT] [--user USER] [--pass PASSWORD] [--path PATH] [--quiet] [--color {never,always,auto}] [exe]
Generate an exploit template
positional arguments:
exe Target binary
options:
-h, --help show this help message and exit
--host HOST Remote host / SSH server
--port PORT Remote port / SSH port
--user USER SSH Username
--pass PASSWORD, --password PASSWORD
SSH Password
--path PATH Remote path of file on SSH server
--quiet Less verbose template comments
--color {never,always,auto}
Print the output in color
用于生成对指定ELF或远程目标的简单攻击脚本模板,这个模板能够让我们不用每一次写攻击脚本的时候都写一遍
from pwn import *
等等相同的东西。
18. unhex
将16进制字符串转化为比特串,与hex功能相反。
19. update
检查pwntools更新。
20. version
pwntools版本。
B. 常数类——constants
常数类Constants,路径/pwnlib/constants/constants.py,继承于python内置类型int,可以通过int方法获取常数的值。在/pwnlib/constants中定义有linux、freebsd、Android等OS,i386、amd64、mips、arm等arch中,源代码中定义的很多常数。如需要i386下Linux系统中的SYS_READ参数:
pwnlib.constants.linux.i386.SYS_READ
。
常数库中的具体内容在pycharm中会有提示,但不知道为什么有的时候不给提示。不过通过查看pwntools源码也可以获取。
C. ROP——ROP类
你还在使用ROPgadget命令获取ELF的gadget吗?你还在用数不清的p64、p32手动构造ROP chain吗?你还在纳闷为什么有些师傅对于一些较为简单的题能够几分钟秒杀拿一血,而你只能一步步手动调试调偏移,眼看着一血加分被别人拿走吗?当我们需要对gadget进行复杂的条件筛选时,ROPgadget配合grep指令确实可以很好地满足需求,但pwntools内置的rop类也相当强大。路径:/pwnlib/rop/rop.py。
用法:rop = ROP([‘yourelf.elf’], base=0x400000, badchars=b’\n’)
关键属性与方法:
- rop.gadgets:获取该ELF文件的所有gadget,返回值为一个字典,key为偏移地址,value为一个Gadget对象,一个Gadget对象具有4个属性:addr——地址,insns——所有指令的列表,regs——影响的寄存器,move——该ROP对栈顶的影响,执行该gadget后若栈上抬则move>0,否则move<=0。
- rop.chain(base: int=None):返回构造的ROP chain的bytes数据,其中base指定该ROP所在的基地址。当该ROP chain与其基地址不相关时可以省略base参数,此时也等同于调用bytes(rop)。
- rop.call(resolvable, arguments: list=()):在ROP chain中添加一个函数调用,其中resolvable传入函数地址,arguments传入参数列表。当ELF中存在的gadgets不足以实现调用功能时,报错。
- rop.raw(value):在ROP chain中直接添加一个值。如果需要将字节数组转换为值,可以使用unpack方法(/pwnlib/util/packing中定义)将其转换为值。
- rop.dump(base: int=None):返回一个字符串,print可以以一种好看的方式打印出当前已经构造的ROP chain。
- rop.<reg>:reg指一个寄存器名,可以返回一个能够控制该寄存器的Gadget实例。
- rop(<reg>=…, …):将ROP实例作为函数调用,传入的参数为寄存器名,值为需要将寄存器修改的值。如
rop(rax=0x1234, rdi=0x2222)
,即可在当前ROP chain中添加修改这些寄存器所需的元素。功能等同于方法rop.setRegisters(registers: dict)。 - rop.migrate(next_base):添加栈迁移ROP元素。注意添加栈迁移元素后,rop.migrated属性将从False变为True,同时将不能再在该ROP chain后继续添加元素,否则报错。
- rop.leave:返回一个带有leave指令的Gadget。"leave ; ret"这个Gadget在函数末尾很常见,该Gadget可以进行栈迁移,这种Gadget的move属性是一个固定的无效值,因为不知道在执行该Gadget时rbp的值是多少。
- rop.find_gadget(instructions: list):精准查询一个Gadget。
- rop.search(self, move=0, regs=None, order=‘size’):查找一个gadget,可选筛选条件有:move——最小的栈变化量,regs——影响的寄存器,order——查询结果的排序方式,只能为字符串"size"和"regs"中的一个。
- rop.build(base=None, description=None):将ROP chain转化为整数构成的列表,可以使用flat方法将其转化为bytes数组。
- rop.from_blob(blob):传入bytes数组,将其转化为ELF后以该ELF为基础创建ROP实例。
- rop.generate_padding(offset, count):使用cyclic的padding生成器生成指定数量的padding数据并返回,offset指从生成器的第几个字节开始,不过作为padding数据,offset是多少不是很重要。
- rop.resolve(resolvable):将ELF文件中的符号解析为对应的地址。
- rop.ret2csu(self, edi=Padding(‘edi’), rsi=Padding(‘rsi’), rdx=Padding(‘rdx’), rbx=Padding(‘rbx’), rbp=Padding(‘rbp’), r12=Padding(‘r12’), r13=Padding(‘r13’), r14=Padding(‘r14’), r15=Padding(‘r15’), call=None):自动构造ret2csu所需的ROP chain,可指定上述寄存器需要赋的值,以及需要调用的函数。仅适用于amd64架构。
- rop.search_iter(move, regs):查找所有能够设置regs参数中所有寄存器且栈上抬字节数大于move参数值的Gadget,以生成器形式返回。
D. SROP——SigReturnFrame类
SigReturnFrame类用于进行srop利用,即通过sigreturn系统调用去调用其他的系统调用。SigReturnFrame维护了一个结构,用于保存各个寄存器的值。srop介绍:传送门
关键属性与方法:
- srop = SigReturnFrame(dict):构造函数,可以传入一个字典(也可以不传参),指定部分寄存器设置的值。
- srop.arch:架构。
- srop.endian:大端序还是小端序。
- srop.values():返回当前构造的Frame字典中的各个值。
- srop.keys():返回当前构造的Frame字典中的各个键。
- srop.<reg>=…:对Frame中对应寄存器的值进行修改。等同于调用srop.set_regvalue(reg, val)
- srop.get_spindex():获取栈寄存器sp所在Frame中的索引。
- unpack_many(bytes(srop)):获取该Frame的字节数组,可用于控制台输入与远程传输。
E. FILE结构体——FileStructure类
FileStructure类维护了_IO_FILE结构体,便于我们进行FSOP。
关键属性与方法:
- fs = FileStructure(null: int=0):构造函数。
- fs.<attr>:获取_IO_FILE结构体中某个元素的值,也可直接进行赋值。
- fs.write(addr: int, size: int):修改fs的_IO_write_base、_IO_write_end、_IO_read_end等属性的值以准备在指定地址处写入指定大小的数据。注意:该函数不能修改vtable,如果需要真的实现write功能,需要另外根据题意修改vtable。
- fs.read(addr: int, size: int):修改fs的_IO_buf_base、_IO_buf_end等属性的值以准备从指定地址开始读取指定大小的数据,与write相同同样需要另外指定vtable。
- fs.orange(io_list_all: int, vtable: int):构造用于进行house_of_orange利用的_IO_FILE结构体,需要给出_IO_list_all地址以及vtable地址。
- fs.struntil(v: str):v是_IO_FILE结构体中的一个属性名,该方法返回_IO_FILE结构体的前一部分,该部分的最后一个属性为v。
F. 格式化字符串漏洞工具——FmtStr类
pwntools针对格式化字符串漏洞设计了一个工具类便于我们构造格式化字符串。
关键属性与方法:
- fmtstr = FmtStr(execute_fmt: function, offset: int = None, padlen: int = 0, numbwritten: int = 0):构造函数。execute_fmt:一个python函数,用于进行交互。这里笔者想对这个交互函数进行一些解释,当offset未指明时,在FmtStr构造函数中,会多次执行execute_fmt函数,pwntools会使用到一个通用格式化字符串漏洞的模板字符串"aaaabaaacaaadaaaeaaaSTART%?$pEND\n",其中?从1递增。我们需要让execute_fmt函数能够获取格式化字符串漏洞中的格式化字符串输出,FmtStr会根据每一次执行该函数的结果自动推断offset的值,这能够节省我们使用gdb手动调试获取offset的时间。offset:该格式化字符串开头对应于格式化字符串的第几个参数,padlen:在前面加上的填充字节数量,numwritten:该格式化字符串之前已经写入的字节数量。
- fmtstr.write(addr, data):构造字符串实现指定地址写指定值。该函数不返回任何值,结果保存在实例中。
- fmtstr.write(addr, data):构造字符串实现指定地址写指定值。该函数不返回任何值。
- fmtstr.execute_writes():将构造的字符串发送到输入流中。
- fmtstr.leak_stack(offset, prefix=b""):泄露栈上指定offset的数据并返回,prefix为注入的字符串的前缀,一般不需另外指定。
- fmtstr.find_offset():查找输入的字符串的索引值。
- **fmtstr_payload(offset, writes, numbwritten=0, write_size=‘byte’, write_size_max=‘long’, overflows=16, strategy=“small”, badbytes=frozenset(), offset_bytes=0)**:独立于FmtStr类,能够直接返回bytes数组的格式化字符串构造函数。几个选项的含义:offset——字符串偏移。writes——字典类型,键为要写入的地址,值为写入的值。numwritten——在该字符串之前已经输出的字符数量。write_size——按照多大的大小写,可选byte、word、dword等,对应1字节、2字节、4字节的一次性写入。badbytes:字节黑名单。
- fmtstr_split(offset, writes, numbwritten=0, write_size=‘byte’, write_size_max=‘long’, overflows=16, strategy=“small”, badbytes=frozenset(), offset_bytes=0):同样是构造字符串,但其返回一个二元组,包含格式化字符串部分和数据部分,如(b’%33c%6$lln%34c%7$hhn’, b’4\x12\x00\x00\x00\x00\x00\x005\x12\x00\x00\x00\x00\x00\x00’)。
G. 数据打包实用函数——pwnlib/util/packing.py
该文件保存有我们常用的各种字节数组与整数、结构体互相转换的实用函数。
- p8/p16/p32/p64(num: int):将整数转化为字符数组,长度1/2/4/8。
- u8/u16/u32/u64(b: bytes):将字符数组转化为整数。
- fit/flat(*args, preprocessor = None, length = None, filler = de_bruijn(), word_size = None, endianness = None, sign = None):将给定参数转化为字符串,参数含义:preprocessor:对每个参数预处理的函数,由用户定义并传入。length:输出长度。filler:用于padding的字符序列,一般默认即可。word_size:字长,即一个参数最终会转化为多少个字节。endianness:大端序或小端序。sign:整数是否有符号。
更多有趣的用法读者可以查阅pwntools官方文档继续探索~
版权归原作者 C0Lin 所有, 如有侵权,请联系我们删除。