[pwn基础]Pwntools学习

Pwntools介绍

Pwntools是一个非常著名的CTF框架漏洞利用开发库,可以让使用者快速的编写exp

它拥有本地执行远程连接读写shellcode生成ROP链构建ELF解析符号泄漏等众多强大的功能。

Pwntools安装

因为他是个python库,所以直接用pip来管理安装即可。

#提前安装pip
sudo apt-get install python3-pip
#安装pwntools
pip install pwntools -i https://pypi.tuna.tsinghua.edu.cn/simple

测试是否安装成功

Pwntools常用模块和函数

Pwntools分为两个模块,一个是pwn,简单的用from pwn import *就能把所有子模块和一些常用的系统库导入当前命名空间中,是专门为了CTF比赛优化的。

另外一个模块是pwnlib,它适合开发成产品,根据自己需要来导入不同的子模块。

  • pwnlib.adb: 安卓adb
  • pwnlib.asm: 汇编和反汇编
  • pwnlib.constans: 包含各种体系结构和操作系统中的系统调用号常量(来自头文件),constants.linux.i386.SYS_stat
  • pwnlib.context: 设置运行环境
  • pwnlib.dynelf: 利用信息泄漏远程解析函数
  • pwnlib.encoders: 对shellcode进行编码,如encoders.encoder.null('xxx')
  • pwnlib.elf: 操作ELF可执行文件和共享库
  • pwnlib.fmtstr: 格式化字符串利用工具
  • pwnlib.gdb: 调试,配合gdb使用
  • pwnlib.libcbd: libc数据库,入libcdb.search_by_build_id('xxx')
  • pwnlib.log: 日志记录管理,比如log.info('hello')
  • pwnlib.memleak: 内存泄漏工具,将泄漏的内存缓存起来,可作为Payload
  • pwnlib.qume: QEMU模拟相关,一般用来模拟不同架构的指令或运行程序
  • pwnlib.rop: ROP利用工具,包括rop,srop等
  • pwnlib.runner: 运行Shellcode,例如:run_assembly('mov eax,SYS_exit;int 0x80;')
  • pwnlib.shellcraft: Shellcode生成器
  • pwnlib.tubes: scokets、ssh、进程管道通信
  • pwnlib.utils: 一些实用小工具,比如CRC计算,cyclic字符串生成等

pwnlib.tubes模块学习

tubes模块是主要用来通信的模块,应该是pwn题中用的最广泛的交互方式,他主要有下面4中通信方式。

  1. pwnlib.tubes.process: 进程通信
  2. pwnlib.tubes.serialtube: 串口通信
  3. pwnlib.tubes.sock: socket套接字通信
  4. pwnlib.tubes.ssh: SSH连接通信

tubes.process

这里我一直好奇这个进程通信他是怎么弄的,为什么我用代码p=process('./mydemo'),然后就可以用send和recv对程序进行发送,然后看了他源码注释 Spawns a new process, and wraps it with a tube for communication.,他应该是用了比较hack的方法,自己模拟了系统加载本地程序变成进程的操作,并且封装了一层管道通信在上面,这样我们就可以通过send、recv来和他创建的进程来进行通信了,所以就给我们创造了无数可能,比如我之前文章[二进制漏洞]栈(Stack)溢出漏洞 Linux篇中里面的题目scanf只能输入ASCII码,这样我们无法构造一个地址Payload,而有了tubes.process则可以轻松做到。

#include <stdio.h>

void hack()
{
printf("Hack Success!!!!\n");
} int main()
{
printf("Hello,Please Start Hack!\n");
char buf[20]={0};
scanf("%s",buf);
printf("Your input:%s\n",buf);
int i;
for(i=0;i<sizeof(buf);i++)
{
printf("0x%x,",buf[i]);
}
return 0;
}
gcc hack2.c -m32 -fno-stack-protector -z noexecstack -o hack2

正常情况下:无法输入0x01、0x02、0x03这种数据。

而用tubes.process则可以用send发送原始十六进制数据。

#导入pwntools模块
from pwn import *
context(arch = 'i386',os='linux')
p = process("./hack2") #显示程序运行的第一条回显
print(p.recv()) #利用pipe管道发送带 十六进制的数据
p.sendline(b'AAAA'+b'\x01\x02\x03\x04') #回显结果
print(p.recvline())
print(p.recvline())

所以[二进制漏洞]栈(Stack)溢出漏洞 Linux篇文章的题,用tubes.process搞起来就方便多了。

打印进程装载起始地址。

p = process("./hack")
imageBase = p.libs()["/home/ubuntu/hack"]

远程的话使用如下命令:

conn = remote('exploitme.example',31337)
conn.recv()
conn.sendline('test')

pwnlib.context(运行环境)

这个模块主要是用来设置进程运行时的环境,比如目标是什么CPU架构,多少位数,什么平台,是否开启日志等等。

#架构32位X86,平台Linux
context(arch='i386',os='linux')
#设置tmux分屏
context.terminal['tmux','splitw','-h']
#开启日志信息
context.log_level = 'debug'

CPU架构如下:

architectures = _longest({
'aarch64': little_64,
'alpha': little_64,
'avr': little_8,
'amd64': little_64,
'arm': little_32,
'cris': little_32,
'i386': little_32,
'ia64': big_64,
'm68k': big_32,
'mips': little_32,
'mips64': little_64,
'msp430': little_16,
'powerpc': big_32,
'powerpc64': big_64,
'riscv': little_32,
's390': big_32,
'sparc': big_32,
'sparc64': big_64,
'thumb': little_32,
'vax': little_32,
'none': {},
})
transform = [('ppc64', 'powerpc64'),
('ppc', 'powerpc'),
('x86-64', 'amd64'),
('x86_64', 'amd64'),
('x86', 'i386'),
('i686', 'i386'),
('armv7l', 'arm'),
('armeabi', 'arm'),
('arm64', 'aarch64')]

位数:

    big_32    = {'endian': 'big', 'bits': 32}
big_64 = {'endian': 'big', 'bits': 64}
little_8 = {'endian': 'little', 'bits': 8}
little_16 = {'endian': 'little', 'bits': 16}
little_32 = {'endian': 'little', 'bits': 32}
little_64 = {'endian': 'little', 'bits': 64}

平台:

 oses = sorted(('linux','freebsd','windows','cgc','android','baremetal'))

pwnlib.elf(ELF文件操作)

pwnlib.elf模块还是挺实用的,虽然linux下有<elf.h>头文件可以用来解析ELF文件,但是很多代码都要自己实现,这个模块就解决了这些实现,可以进行符号查找、虚拟内存、文件偏移、修改和保存二进制等等。

from pwnlib.elf import ELF
#构造类
elf = ELF('./hack_dyn') #架构,位数,平台
print("---------------------------------------------")
print("[+]架构:{0} 位数:{1} 系统:{2}".format(elf.arch,elf.bits,elf.os))
print("")
#打印装载地址
print("[*]装载地址:",hex(elf.address))
#打印GOT表
print("[*]GOT表:")
for kv in elf.got.items():
print(kv)
#打印PLT表
print("")
print("[*]PLT表:")
for kv in elf.plt.items():
print(kv)
print("[*]hack函数偏移:",hex(elf.symbols['hack']))
print("---------------------------------------------")

可以看到打印出了GOT表、PLT表、符号表中hack函数的偏移,其中装载地址为0是因为这是个动态链接程序,装载地址不确定,改成静态编译就能显示装载地址。

静态链接

asm(address,assembly) # 汇编指令assembly插入ELF的address地址处,需要使用save函数来保存
bss(offset) # 返回.bss段加上offset后的地址
checksec() # 查看文件开启的安全保护
disable_nx() # 关闭NX
disasm(address,n_bytes) # 返回地址address反汇编n字节的字符串
offset_to_vaddr(offset) # 将偏移offset转换为虚拟地址
vaddr_to_offset(address) # 从虚拟地址address转换为文件偏移
read(address,count) # 从虚拟地址address读取count个字节的数据
write(address,data) # 在虚拟地址address写入data
section(name) # 获取name段的数据
debug() # 使用gdb.debug()进行调试

pwnlib.asm(汇编模块)

这是个很强大的模块,可以进行汇编和反汇编,通常用来开发Shellcode的时候非常有用。

可以用pwnlib.context先设置CPU架构字节序位数。

asm()函数进行汇编,用disasm()函数进行反汇编

from pwnlib.asm import *
#汇编
print(asm('mov eax, 0'))
print(asm('mov ebx, 1'))
print(asm('add eax, ebx'))
print(asm('mov eax, SYS_execve'))
print(asm('nop'))

disasm()反汇编

from pwnlib.asm  import *
from pwnlib.util.fiddling import *
#反汇编
print(disasm(unhex('E007BFA9E20FBFA9E417BFA9E61FBFA9E827BFA9FA6FBFA9FC77BFA9FE0F1FF8C81580D2010000D440050035881580D2010000D41F040071C1040054000080D261FCFF10021880D2E3031FAA080780D2010000D4E003F837FF4300D1481680D2010000D4E00B00B9881B80D2E4230091E3031FAAE2031FAAE1031FAA200280D20024A0F2010000D4E00B00B9FF43009100020035000080D241FAFF300201A0D2E3031FAA080780D2010000D4E40300AAC81B80D2000080D2230080D2E5031FAAA20080D2010094D20100A0F2010000D4FE0741F8FC77C1A8FA6FC1A8E827C1A8E61FC1A8E417C1A8E20FC1A8E007C1A8FD7BBEA9010000142C010000000000000000000000000000000000000000000000000000AA0400000000F1FF'),arch='aarch64',bits=64))

当汇编反汇编,其他架构平台时候,记得要安装对应的Binutils,安装教程:https://docs.pwntools.com/en/stable/install/binutils.html

反编译效果,真的很强大!

pwnlib.shellcraft(Shellcode生成器)

这个模块可以用来生成Shellcode代码,这种模块简直太爱了,他可以生成aarch64、arm、thumb、mips、i386、amd64、powerpc架构的shellcode代码,基本上的架构都有了。

生成Shellcode代码。

>>> from pwn import *
>>> print(shellcraft.i386.nop())
nop
#生成了一个x86架构平台的nop

接下来生成一个Android手机打开/data/local/tmp/test.txt的Shellcode,这模块太强大了。

#设置CPU架构  aarch64
#设置系统平台 android
print(shellcraft.aarch64.android.open('/data/local/tmp/test.txt'))

还能配合asm输出不同格式shellcode,实在是太方便、太好用了!

from pwn import *

shellcode = shellcraft.aarch64.android.open('/data/local/tmp/test.txt')
print("输出字符串格式Shellcode:")
print(asm(shellcode,arch='aarch64',bits=64,os='android'))
print("")
print("输出十六进制格式Shellcode:")
print(asm(shellcode,arch='aarch64',bits=64,os='android').hex())

官方的例子。

from pwnlib.shellcraft import *
context.clear()
context.arch = 'amd64'
sc = 'push rbp; mov rbp, rsp;'
sc += shellcraft.echo('Hello\n')
sc += 'mov rsp, rbp; pop rbp; ret'
solib = make_elf_from_assembly(sc, shared=1)
subprocess.check_output(['echo', 'World'], env={'LD_PRELOAD': solib}, universal_newlines = True)
'Hello\nWorld\n'

pwnlib.util(小工具)

这个模块是一些常用的功能函数,比如之前用到过的unhex就来自这模块,除此之外还有packing、hashes、net、misc、sh_string、cyclic等函数。

#用的最多的应该是pack函数了吧
p8(0) #打包1字节
b'\x00'
p32(0xdeadbeef) #32位最常用的,打包4字节
b'\xef\xbe\xad\xde'
p64(0xdeadbeef)
b'\xef\xbe\xad\xde\x00\x00\x00\x00'
#可设置大小端序
>>> p32(0xdeadbeef,endian='little')
b'\xef\xbe\xad\xde'
>>> p32(0xdeadbeef,endian='big')
b'\xde\xad\xbe\xef'
#解包
unpack(b'\xaa\x55',16,endian='little')
'0x55aa'
u32('\xaa\x55\x00\x00')
21930
u64('\xaa\x55\x00\x00\x00\x00\x00\x00')
21930 #生成溢出字符串(cyclic)
cyclic(20)
b'aaaabaaacaaadaaaeaaa'
cyclic(20, alphabet=string.ascii_uppercase) #全大写
b'AAAABAAACAAADAAAEAAA'
cyclic(20, n=8) #8字符对齐
b'aaaaaaaabaaaaaaacaaa'
cyclic(20, n=2) #2字符对齐
b'aabacadaeafagahaiaja'
cyclic(alphabet = "ABC", n = 3)#设置成ABC对齐
b'AAABAACABBABCACBACCBBBCBCCC'
context.cyclic_alphabet = "ABC" #全局修改
cyclic(10)
b'AAAABAAACA'
#查找偏移
cyclic_find('daaa')
12
cyclic_find(0x61616162)
4 #unhex
unhex('0102030405060708')
b'\x01\x02\x03\x04\x05\x06\x07\x08'

pwnlib.rop

rop利用模块,包括rop,srop等。

现在的exploit是越来越难,一般起手题都得是NX开启的,ROP这种以前都能出400分题的技术现在也就出50-100分题了非常惨,也许跟这个工具简化了ROP过程有关系?「误」

先简单回顾一下ROP的原理,由于NX开启不能在栈上执行shellcode,我们可以在栈上布置一系列的返回地址与参数,这样可以进行多次的函数调用,通过函数尾部的ret语句控制程序的流程,而用程序中的一些pop/ret的代码块(称之为gadget)来平衡堆栈。其完成的事情无非就是放上/bin/sh,覆盖程序中某个函数的GOT为system的,然后ret到那个函数的plt就可以触发system('/bin/sh')。由于是利用ret指令的exploit,所以叫Return-Oriented Programming。(如果没有开启ASLR,可以直接使用ret2libc技术)

好,这样来看,这种技术的难点自然就是如何在栈上布置返回地址以及函数参数了。而ROP模块的作用,就是自动地寻找程序里的gadget,自动在栈上部署对应的参数。

from pwn import *

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

参考文章:

http://unbelievable.cool/2021/07/25/pwntools学习/#pwnlib-asm (pwntools学习)

http://brieflyx.me/2015/python-module/pwntools-intro/ (Exploit利器--Pwntools)

http://www.leonlist.top/2020/09/02/pwn基本工具-pwntools/ (pwn基本工具-pwntools)

https://xuanxuanblingbling.github.io/ctf/pwn/2020/12/13/getshell3/ (Getshell远程:真·RCE 正连?反连?不连?)

https://github.com/Gallopsled/pwntools (pwntools源码)

最后大家可以多看看pwntools源码去熟悉熟悉,每个模块的功能,他注释一般有demo写的还是非常详细的。

PWN菜鸡小分队

最后感谢大家的阅读,本菜鸡也是刚学,文章中如有错误请及时指出。

大家也可以来群里骂我哈哈哈,群里有PWN、RE、WEB大佬,欢迎交流

[pwn基础]Pwntools学习的更多相关文章

  1. PWN二进制漏洞学习指南

    目录 PWN二进制漏洞学习指南 前言 前置技能 PWN概念 概述 发音 术语 PWN环境搭建 PWN知识学习途径 常见漏洞 安全机制 PWN技巧 PWN相关资源博客 Pwn菜鸡小分队 PWN二进制漏洞 ...

  2. 【pwn】学pwn日记——栈学习(持续更新)

    [pwn]学pwn日记--栈学习(持续更新) 前言 从8.2开始系统性学习pwn,在此之前,学习了部分汇编指令以及32位c语言程序的堆栈图及函数调用. 学习视频链接:XMCVE 2020 CTF Pw ...

  3. [pwn基础]动态链接原理

    目录 [pwn基础]动态链接原理 动态链接概念 动态链接调用so例子 GOT(全局偏移表) got表劫持小实验 PLT(延迟绑定) PLT概念 延迟绑定(PLT表) 实战学习 [pwn基础]动态链接原 ...

  4. [pwn基础] Linux安全机制

    目录 [pwn基础] Linux安全机制 Canary(栈溢出保护) 开启关闭Cannary Canary的种类 Terminator canaries(终结者金丝雀) Random cannarie ...

  5. 零基础如何学习java更有效呢?

    零基础学java,不知道该如何入手?也不知道学习的方向,很多人会问零基础怎么样学习,有没有什么入门的书籍推荐:只要方法正确,零基础学好java也是有机会的哦. 一.理解Java思想 Java是一门面向 ...

  6. Swift基础语法学习总结(转)

    Swift基础语法学习总结 1.基础  1.1) swift还是使用// 和/* */ 来注释,并且/* */允许多行注释. 1.2) swift使用print和println打印,它的传参是一个泛型 ...

  7. Java基础知识学习(九)

    GUI开发 先前用Java编写GUI程序,是使用抽象窗口工具包AWT(Abstract Window Toolkit).现在多用Swing.Swing可以看作是AWT的改良版,而不是代替AWT,是对A ...

  8. .Net程序员之Python基础教程学习----列表和元组 [First Day]

    一. 通用序列操作: 其实对于列表,元组 都属于序列化数据,可以通过下表来访问的.下面就来看看序列的基本操作吧. 1.1 索引: 序列中的所有元素的下标是从0开始递增的. 如果索引的长度的是N,那么所 ...

  9. objective-c基础教程——学习小结

    objective-c基础教程——学习小结   提纲: 简介 与C语言相比要注意的地方 objective-c高级特性 开发工具介绍(cocoa 工具包的功能,框架,源文件组织:XCode使用介绍) ...

随机推荐

  1. 轻量化安装 TKEStack:让已有 K8s 集群拥有企业级容器云平台的能力

    关于我们 更多关于云原生的案例和知识,可关注同名[腾讯云原生]公众号~ 福利: ①公众号后台回复[手册],可获得<腾讯云原生路线图手册>&<腾讯云原生最佳实践>~ ②公 ...

  2. kubectl creat -f 创建pod时出错

    如果创建yaml时候,sts中已经存在,但是get pod又查不到已经启动的pod可以这样 [root@k3master src]# kubectl get pod //查不到eureka NAME ...

  3. keytools命令生成证书

    平时开发中可以使用keytools命令生成证书,一般常用格式为: keytool -genkey -alias tzzxxt -keyalg RSA -keypass 123456 -validity ...

  4. 【高并发】不得不说的线程池与ThreadPoolExecutor类浅析

    大家好,我是冰河~~ 今天,我们一起来简单聊聊线程池中的ThreadPoolExecutor类,好了,不多说了,开始进入今天的正题. 一.抛砖引玉 既然Java中支持以多线程的方式来执行相应的任务,但 ...

  5. MySQL数据库常识之储存引擎

    我的博客 储存引擎分类 show engines; 这个命令可以查看数据库的数据引擎,可以看到InnoDB是默认的引擎. 命令除了在终端运行,也可以在查询数据库可视化工具中运行. 而,(我是5.7版本 ...

  6. iptables系列教程(一)| iptables入门篇

    一个执着于技术的公众号 前言 在早期的 Linux 系统中,默认使用的是 iptables 配置防火墙.尽管新型 的 firewalld 防火墙已经被投入使用多年,但是大量的企业在生产环境中依然出于各 ...

  7. uniapp中利用uni.$emit()和uni.$on()进行页面和tabbar页面传值(页面通讯)

    tabbar页面 <script> export default { data() { return { list: [] , }; }, onLoad() { // 监听事件 uni.$ ...

  8. MongoDB是什么?非关系型数据库的优点?安装使用教程

    哈喽!大家好,我是小奇,一位热爱分享的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 书接上回,由于球姐都有孩子了, ...

  9. 992. Sort Array By Parity II - LeetCode

    Question 992. Sort Array By Parity II Solution 题目大意:给一个int数组,一半是奇数一半是偶数,分别对偶数数和奇数数排序并要求这个数本身是偶数要放在偶数 ...

  10. Java高并发-Java内存模型和线程安全

    一.原子性 原子性是指一个操作是不可中断的.即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰. i++是原子操作吗? 不是,包含3个操作:读i,i=i+1,写i 32位的机子上读取 ...